compel 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -20
  3. data/lib/compel/builder/array.rb +31 -0
  4. data/lib/compel/builder/boolean.rb +7 -0
  5. data/lib/compel/builder/common.rb +2 -2
  6. data/lib/compel/builder/common_value.rb +4 -4
  7. data/lib/compel/builder/hash.rb +3 -1
  8. data/lib/compel/builder/methods.rb +5 -0
  9. data/lib/compel/builder/schema.rb +4 -0
  10. data/lib/compel/coercion/coercion.rb +47 -0
  11. data/lib/compel/coercion/nil_result.rb +13 -0
  12. data/lib/compel/coercion/result.rb +37 -0
  13. data/lib/compel/coercion/types/array.rb +15 -0
  14. data/lib/compel/coercion/{boolean.rb → types/boolean.rb} +1 -3
  15. data/lib/compel/coercion/types/date.rb +36 -0
  16. data/lib/compel/coercion/types/datetime.rb +36 -0
  17. data/lib/compel/coercion/{float.rb → types/float.rb} +2 -2
  18. data/lib/compel/coercion/{hash.rb → types/hash.rb} +2 -2
  19. data/lib/compel/coercion/{integer.rb → types/integer.rb} +2 -2
  20. data/lib/compel/coercion/{json.rb → types/json.rb} +2 -2
  21. data/lib/compel/coercion/{regexp.rb → types/regexp.rb} +2 -4
  22. data/lib/compel/coercion/{string.rb → types/string.rb} +3 -5
  23. data/lib/compel/coercion/types/time.rb +36 -0
  24. data/lib/compel/coercion/types/type.rb +29 -0
  25. data/lib/compel/contract.rb +14 -42
  26. data/lib/compel/errors.rb +13 -17
  27. data/lib/compel/exceptions/invalid_object_error.rb +9 -0
  28. data/lib/compel/result.rb +30 -0
  29. data/lib/compel/validation.rb +6 -4
  30. data/lib/compel/validators/array_validator.rb +107 -0
  31. data/lib/compel/validators/base.rb +11 -1
  32. data/lib/compel/validators/hash_validator.rb +77 -19
  33. data/lib/compel/validators/type_validator.rb +42 -11
  34. data/lib/compel/version.rb +1 -1
  35. data/lib/compel.rb +6 -15
  36. data/spec/compel/builder_spec.rb +354 -144
  37. data/spec/compel/coercion_spec.rb +16 -1
  38. data/spec/compel/compel_spec.rb +315 -302
  39. data/spec/compel/validation_spec.rb +97 -115
  40. data/spec/support/sinatra_app.rb +2 -2
  41. metadata +21 -15
  42. data/lib/compel/coercion/date.rb +0 -30
  43. data/lib/compel/coercion/datetime.rb +0 -30
  44. data/lib/compel/coercion/time.rb +0 -30
  45. data/lib/compel/coercion/type.rb +0 -17
  46. data/lib/compel/coercion.rb +0 -31
  47. data/lib/compel/exceptions/invalid_hash_error.rb +0 -9
@@ -2,219 +2,429 @@ describe Compel::Builder do
2
2
 
3
3
  context 'Schema' do
4
4
 
5
- subject(:builder) { Compel::Builder::String.new }
6
-
7
- it 'shoudl' do
8
- object = {
9
- first_name: 'Joaquim',
10
- birth_date: '1989-0',
11
- address: {
12
- line_one: 'Lisboa',
13
- post_code: '1100',
14
- country_code: 'PT'
15
- }
16
- }
17
-
18
- schema = Compel.hash.keys({
19
- first_name: Compel.string.required,
20
- last_name: Compel.string.required,
21
- birth_date: Compel.date.iso8601,
22
- address: Compel.hash.keys({
23
- line_one: Compel.string.required,
24
- line_two: Compel.string.default('-'),
25
- post_code: Compel.string.format(/^\d{4}-\d{3}$/).required,
26
- country_code: Compel.string.in(['PT', 'GB']).default('PT')
27
- })
28
- })
29
-
30
- result = Compel.run(object, schema)
31
-
32
- expect(result).to \
33
- eq({
34
- "first_name" => "Joaquim",
35
- "birth_date" => "1989-0",
36
- "address" => {
37
- "line_one" => "Lisboa",
38
- "post_code" => "1100",
39
- "country_code" => "PT",
40
- "line_two" => "-"
41
- },
42
- "errors" => {
43
- "last_name" => ["is required"],
44
- "birth_date" => ["'1989-0' is not a parsable date with format: %Y-%m-%d"],
45
- "address" => {
46
- "post_code" => ["must match format ^\\d{4}-\\d{3}$"]
47
- }
48
- }
49
- })
50
- end
5
+ context 'Build' do
51
6
 
52
- it 'should build new Schema for givin type' do
53
- expect(builder.type).to be(Compel::Coercion::String)
54
- expect(builder.options.keys).to eq([])
55
- expect(builder.required?).to be false
56
- expect(builder.default_value).to be nil
57
- end
7
+ it 'should build new Schema for givin type' do
8
+ builder = Compel.string
58
9
 
59
- it 'should build Schema' do
60
- schema = Compel.hash.keys({
61
- a: Compel.float,
62
- b: Compel.string,
63
- c: Compel.hash.keys({
64
- cc: Compel.integer.length(1)
65
- }),
66
- d: Compel.json,
67
- e: Compel.time,
68
- f: Compel.datetime,
69
- g: Compel.date,
70
- h: Compel.integer
71
- })
72
-
73
- keys_schemas = schema.options[:keys]
74
-
75
- expect(keys_schemas.a.type).to be Compel::Coercion::Float
76
- expect(keys_schemas.b.type).to be Compel::Coercion::String
77
- expect(keys_schemas.c.type).to be Compel::Coercion::Hash
78
- expect(keys_schemas.d.type).to be Compel::Coercion::JSON
79
- expect(keys_schemas.e.type).to be Compel::Coercion::Time
80
- expect(keys_schemas.f.type).to be Compel::Coercion::DateTime
81
- expect(keys_schemas.g.type).to be Compel::Coercion::Date
82
- expect(keys_schemas.h.type).to be Compel::Coercion::Integer
83
- end
10
+ expect(builder.type).to be(Compel::Coercion::String)
11
+ expect(builder.options.keys).to eq([])
12
+ expect(builder.required?).to be false
13
+ expect(builder.default_value).to be nil
14
+ end
84
15
 
85
- it 'should validate hash object from schema' do
86
- schema = Compel.hash.keys({
87
- a: Compel.float.required
88
- })
16
+ it 'should build Schema' do
17
+ schema = Compel.hash.keys({
18
+ a: Compel.float,
19
+ b: Compel.string,
20
+ c: Compel.hash.keys({
21
+ cc: Compel.integer.length(1)
22
+ }),
23
+ d: Compel.json,
24
+ e: Compel.time,
25
+ f: Compel.datetime,
26
+ g: Compel.date,
27
+ h: Compel.integer
28
+ })
89
29
 
90
- expect(schema.validate({ a: nil }).errors.a).to \
91
- include('is required')
92
- end
30
+ keys_schemas = schema.options[:keys]
31
+
32
+ expect(keys_schemas.a.type).to be Compel::Coercion::Float
33
+ expect(keys_schemas.b.type).to be Compel::Coercion::String
34
+ expect(keys_schemas.c.type).to be Compel::Coercion::Hash
35
+ expect(keys_schemas.d.type).to be Compel::Coercion::JSON
36
+ expect(keys_schemas.e.type).to be Compel::Coercion::Time
37
+ expect(keys_schemas.f.type).to be Compel::Coercion::DateTime
38
+ expect(keys_schemas.g.type).to be Compel::Coercion::Date
39
+ expect(keys_schemas.h.type).to be Compel::Coercion::Integer
40
+ end
41
+
42
+ context 'Builder::CommonValue' do
93
43
 
94
- context 'Builder::CommonValue' do
44
+ context '#in, #range, #min, #max' do
95
45
 
96
- context '#in, #range, #min, #max' do
46
+ subject(:builder) { Compel.string }
97
47
 
98
- it 'should have value' do
99
- [:in, :range, :min, :max].each do |method|
100
- builder.send(method, "#{method}")
101
- expect(builder.options[method]).to eq("#{method}")
48
+ it 'should have value' do
49
+ [:in, :range, :min, :max].each do |method|
50
+ builder.send(method, "#{method}")
51
+ expect(builder.options[method]).to eq("#{method}")
52
+ end
102
53
  end
54
+
103
55
  end
104
56
 
105
57
  end
106
58
 
107
- end
59
+ context 'Builder::Common' do
60
+
61
+ subject(:builder) { Compel.string }
108
62
 
109
- context 'Builder::Common' do
63
+ context '#is, #default' do
110
64
 
111
- context '#is, #default' do
65
+ it 'should have value' do
112
66
 
113
- it 'should have value' do
114
- [:is, :default].each do |method|
115
- builder.send(method, "#{method}")
116
- expect(builder.options[method]).to eq("#{method}")
67
+ [:is, :default].each do |method|
68
+ builder.send(method, "#{method}")
69
+ expect(builder.options[method]).to eq("#{method}")
70
+ end
117
71
  end
72
+
118
73
  end
119
74
 
120
- end
75
+ context '#required' do
76
+
77
+ it 'should be false' do
78
+ expect(builder.required?).to be false
79
+ end
80
+
81
+ it 'should be true' do
82
+ builder.required
121
83
 
122
- context '#required' do
84
+ expect(builder.required?).to be true
85
+ end
123
86
 
124
- it 'should be false' do
125
- expect(builder.required?).to be false
126
87
  end
127
88
 
128
- it 'should be true' do
129
- builder.required
89
+ context '#length' do
90
+
91
+ it 'should have value' do
92
+ builder.length(5)
93
+
94
+ expect(builder.options[:length]).to be 5
95
+ end
96
+
97
+ it 'should raise exception for invalid type' do
98
+ expect { builder.length('abc') }.to \
99
+ raise_error Compel::TypeError, "'abc' is not a valid Integer"
100
+ end
130
101
 
131
- expect(builder.required?).to be true
132
102
  end
133
103
 
134
104
  end
135
105
 
136
- context '#length' do
106
+ context 'Date' do
137
107
 
138
- it 'should have value' do
139
- builder.length(5)
108
+ subject(:builder) { Compel.date }
140
109
 
141
- expect(builder.options[:length]).to be 5
142
- end
110
+ context '#format' do
111
+
112
+ it 'should have value' do
113
+ builder.format('%d-%m-%Y')
114
+
115
+ expect(builder.options[:format]).to eq('%d-%m-%Y')
116
+ end
117
+
118
+ it 'should have value' do
119
+ builder.iso8601
120
+
121
+ expect(builder.options[:format]).to eq('%Y-%m-%d')
122
+ end
143
123
 
144
- it 'should raise exception for invalid type' do
145
- expect { builder.length('abc') }.to \
146
- raise_error Compel::TypeError, "'abc' is not a valid Integer"
147
124
  end
148
125
 
149
126
  end
150
127
 
151
- end
128
+ context 'String' do
152
129
 
153
- context 'Date' do
130
+ subject(:builder) { Compel.string }
154
131
 
155
- subject(:builder) { Compel::Builder::Date.new }
132
+ context '#format' do
156
133
 
157
- context '#format' do
134
+ it 'should raise exception for invalid type' do
135
+ expect { builder.format('abc') }.to \
136
+ raise_error Compel::TypeError, "'abc' is not a valid Regexp"
137
+ end
138
+
139
+ it 'should have value' do
140
+ builder.format(/1/)
158
141
 
159
- it 'should have value' do
160
- builder.format('%d-%m-%Y')
142
+ expect(builder.options[:format]).to eq(/1/)
143
+ end
161
144
 
162
- expect(builder.options[:format]).to eq('%d-%m-%Y')
163
145
  end
164
146
 
165
- it 'should have value' do
166
- builder.iso8601
147
+ context '#min_length' do
148
+
149
+ it 'should raise exception for invalid type' do
150
+ expect { builder.min_length('a') }.to \
151
+ raise_error Compel::TypeError, "'a' is not a valid Integer"
152
+ end
153
+
154
+ it 'should have value' do
155
+ builder.min_length(4)
156
+
157
+ expect(builder.options[:min_length]).to eq(4)
158
+ end
159
+
160
+ end
161
+
162
+ context '#max_length' do
163
+
164
+ it 'should raise exception for invalid type' do
165
+ expect { builder.max_length('a') }.to \
166
+ raise_error Compel::TypeError, "'a' is not a valid Integer"
167
+ end
168
+
169
+ it 'should have value' do
170
+ builder.max_length(10)
171
+
172
+ expect(builder.options[:max_length]).to eq(10)
173
+ end
167
174
 
168
- expect(builder.options[:format]).to eq('%Y-%m-%d')
169
175
  end
170
176
 
171
177
  end
172
178
 
173
179
  end
174
180
 
175
- context 'String' do
181
+ context 'Validate' do
182
+
183
+ context 'Hash' do
176
184
 
177
- context '#format' do
185
+ it 'should validate Hash schema' do
186
+ object = {
187
+ first_name: 'Joaquim',
188
+ birth_date: '1989-0',
189
+ address: {
190
+ line_one: 'Lisboa',
191
+ post_code: '1100',
192
+ country_code: 'PT'
193
+ }
194
+ }
178
195
 
179
- it 'should raise exception for invalid type' do
180
- expect { builder.format('abc') }.to \
181
- raise_error Compel::TypeError, "'abc' is not a valid Regexp"
196
+ schema = Compel.hash.keys({
197
+ first_name: Compel.string.required,
198
+ last_name: Compel.string.required,
199
+ birth_date: Compel.date.iso8601,
200
+ address: Compel.hash.keys({
201
+ line_one: Compel.string.required,
202
+ line_two: Compel.string.default('-'),
203
+ post_code: Compel.string.format(/^\d{4}-\d{3}$/).required,
204
+ country_code: Compel.string.in(['PT', 'GB']).default('PT')
205
+ })
206
+ })
207
+
208
+ result = schema.validate(object)
209
+
210
+ expect(result.value).to \
211
+ eq({
212
+ "first_name" => "Joaquim",
213
+ "birth_date" => "1989-0",
214
+ "address" => {
215
+ "line_one" => "Lisboa",
216
+ "post_code" => "1100",
217
+ "country_code" => "PT",
218
+ "line_two" => "-"
219
+ },
220
+ "errors" => {
221
+ "last_name" => ["is required"],
222
+ "birth_date" => ["'1989-0' is not a parsable date with format: %Y-%m-%d"],
223
+ "address" => {
224
+ "post_code" => ["must match format ^\\d{4}-\\d{3}$"]
225
+ }
226
+ }
227
+ })
182
228
  end
183
229
 
184
- it 'should have value' do
185
- builder.format(/1/)
230
+ it 'should validate hash object from schema' do
231
+ schema = Compel.hash.keys({
232
+ a: Compel.float.required
233
+ })
186
234
 
187
- expect(builder.options[:format]).to eq(/1/)
235
+ expect(schema.validate({ a: nil }).errors.a).to \
236
+ include('is required')
188
237
  end
189
238
 
190
239
  end
191
240
 
192
- context '#min_length' do
241
+ context 'String' do
193
242
 
194
- it 'should raise exception for invalid type' do
195
- expect { builder.min_length('a') }.to \
196
- raise_error Compel::TypeError, "'a' is not a valid Integer"
243
+ it 'should validate Type schema' do
244
+ schema = Compel.string.format(/^\d{4}-\d{3}$/).required
245
+ response = schema.validate('1234')
246
+
247
+ expect(response.errors).to \
248
+ include("must match format ^\\d{4}-\\d{3}$")
197
249
  end
198
250
 
199
- it 'should have value' do
200
- builder.min_length(4)
251
+ end
252
+
253
+ context 'Array' do
254
+
255
+ subject(:builder) { Compel.array }
256
+
257
+ it 'should validate nil without errors' do
258
+ result = builder.validate(nil)
259
+
260
+ expect(result.valid?).to be true
261
+ end
262
+
263
+ it 'should validate nil with errors' do
264
+ result = builder.required.validate(nil)
265
+
266
+ expect(result.errors[:base]).to include('is required')
267
+ end
268
+
269
+ it 'should validate with errors for invalid array' do
270
+ result = builder.required.validate(1)
271
+
272
+ expect(result.errors[:base]).to include("'1' is not a valid Array")
273
+ end
274
+
275
+ context '#items' do
276
+
277
+ it 'should validate without items' do
278
+ result = builder.validate([1, 2, 3])
279
+
280
+ expect(result.valid?).to be true
281
+ expect(result.value).to eq([1, 2, 3])
282
+ end
283
+
284
+ it 'should raise exception for invalid type' do
285
+ expect { builder.items('a') }.to \
286
+ raise_error Compel::TypeError, "#items must be a valid Schema"
287
+ end
288
+
289
+ it 'should raise exception for invalid type' do
290
+ expect { builder.items('a') }.to \
291
+ raise_error Compel::TypeError, "#items must be a valid Schema"
292
+ end
293
+
294
+ it 'should have value' do
295
+ builder.items(Compel.integer)
296
+
297
+ expect(builder.options[:items].class).to be(Compel::Builder::Integer)
298
+ end
299
+
300
+ it 'should validate all items' do
301
+ builder.items(Compel.integer)
302
+
303
+ result = builder.validate([1, '2', nil])
304
+
305
+ expect(result.valid?).to be true
306
+ expect(result.value).to eq([1, 2])
307
+ end
308
+
309
+ it 'should validate all items with errors' do
310
+ builder.items(Compel.float.required)
311
+
312
+ result = builder.validate([1, 'a', nil])
313
+
314
+ expect(result.valid?).to be false
315
+ expect(result.errors['1']).to include("'a' is not a valid Float")
316
+ expect(result.errors['2']).to include('is required')
317
+ end
318
+
319
+ it 'should coerce all hash items' do
320
+ builder.items(Compel.hash.keys({
321
+ a: Compel.string.required,
322
+ b: Compel.integer
323
+ }))
324
+
325
+ result = builder.validate([
326
+ { a: 'A', b: '1' },
327
+ { a: 'B' },
328
+ { a: 'C', b: 3 },
329
+ ])
330
+
331
+ expect(result.valid?).to be true
332
+ expect(result.value).to eq \
333
+ [
334
+ Hashie::Mash.new({ a: 'A', b: 1 }),
335
+ Hashie::Mash.new({ a: 'B' }),
336
+ Hashie::Mash.new({ a: 'C', b: 3 })
337
+ ]
338
+ end
339
+
340
+ it 'should coerce all hash items with errors' do
341
+ builder.items(Compel.hash.keys({
342
+ a: Compel.string.required,
343
+ b: Compel.string.format(/^abc$/).required
344
+ }))
345
+
346
+ result = builder.validate([
347
+ { a: 'A', b: 'abc' },
348
+ { a: 'B' },
349
+ { a: 'C', b: 'abcd' },
350
+ ])
351
+
352
+ expect(result.valid?).to be false
353
+ expect(result.errors['1'][:b]).to include('is required')
354
+ expect(result.errors['2'][:b]).to include('must match format ^abc$')
355
+
356
+ expect(result.value[0][:a]).to eq('A')
357
+ expect(result.value[0][:b]).to eq('abc')
358
+
359
+ expect(result.value[1][:a]).to eq('B')
360
+ expect(result.value[1][:b]).to be_nil
361
+ expect(result.value[1][:errors][:b]).to include('is required')
362
+
363
+ expect(result.value[2][:a]).to eq('C')
364
+ expect(result.value[2][:b]).to eq('abcd')
365
+ expect(result.value[2][:errors][:b]).to \
366
+ include('must match format ^abc$')
367
+ end
368
+
369
+ it 'should coerce array with hash items and nested array keys with errors' do
370
+ builder.items(Compel.hash.keys({
371
+ a: Compel.string,
372
+ b: Compel.array.items(Compel.integer.required).required
373
+ }))
374
+
375
+ result = builder.validate([
376
+ { a: 'C' },
377
+ { b: [1, 2, 3] },
378
+ { b: ['1', nil, 'a'] }
379
+ ])
380
+
381
+ expect(result.valid?).to be false
382
+
383
+ expect(result.value[0][:a]).to eq('C')
384
+ expect(result.value[0][:b]).to be_nil
385
+ expect(result.value[1][:a]).to be_nil
386
+ expect(result.value[1][:b]).to eq([1, 2, 3])
387
+ expect(result.value[2][:a]).to be_nil
388
+ expect(result.value[2][:b]).to eq([1, nil, 'a'])
389
+
390
+ expect(result.errors['0'][:b]).to include('is required')
391
+ expect(result.errors['2'][:b]['1']).to include('is required')
392
+ expect(result.errors['2'][:b]['2']).to \
393
+ include("'a' is not a valid Integer")
394
+ end
201
395
 
202
- expect(builder.options[:min_length]).to eq(4)
203
396
  end
204
397
 
205
398
  end
206
399
 
207
- context '#max_length' do
400
+ context 'Hash' do
401
+
402
+ subject(:builder) { Compel.hash }
208
403
 
209
- it 'should raise exception for invalid type' do
210
- expect { builder.max_length('a') }.to \
211
- raise_error Compel::TypeError, "'a' is not a valid Integer"
404
+ it 'should validate empty keys option' do
405
+ schema = builder.required
406
+
407
+ expect(schema.validate({ a: 1 }).valid?).to be true
408
+ end
409
+
410
+ it 'should validate nil' do
411
+ schema = builder.required
412
+
413
+ result = schema.validate(nil)
414
+
415
+ expect(result.valid?).to be false
416
+ expect(result.errors[:base]).to \
417
+ include('is required')
212
418
  end
213
419
 
214
- it 'should have value' do
215
- builder.max_length(10)
420
+ it 'should validate empty keys with errors' do
421
+ schema = builder.required.length(2)
422
+
423
+ result = schema.validate({ a: 1 })
216
424
 
217
- expect(builder.options[:max_length]).to eq(10)
425
+ expect(result.valid?).to be false
426
+ expect(result.errors[:base]).to \
427
+ include('cannot have length different than 2')
218
428
  end
219
429
 
220
430
  end
@@ -73,7 +73,7 @@ describe Compel::Coercion do
73
73
  end
74
74
 
75
75
  it 'should coerce with format' do
76
- value = Compel::Coercion.coerce!('22-12-2015', Date, { format: '%d-%m-%Y'})
76
+ value = Compel::Coercion.coerce!('22-12-2015', Date, { format: '%d-%m-%Y' })
77
77
 
78
78
  expect(value).to eq(Date.strptime('22-12-2015', '%d-%m-%Y'))
79
79
  end
@@ -248,6 +248,21 @@ describe Compel::Coercion do
248
248
 
249
249
  end
250
250
 
251
+ context 'Array' do
252
+
253
+ it 'should not coerce' do
254
+ expect { Compel::Coercion.coerce!(123, Compel::Coercion::Array) }.to \
255
+ raise_error Compel::TypeError, "'123' is not a valid Array"
256
+ end
257
+
258
+ it 'should coerce' do
259
+ value = Compel::Coercion.coerce!([1, 2], Compel::Coercion::Array)
260
+
261
+ expect(value).to eq([1, 2])
262
+ end
263
+
264
+ end
265
+
251
266
  end
252
267
 
253
268
  end