schemacop 2.1.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +58 -0
  3. data/LICENSE +1 -1
  4. data/README.md +250 -16
  5. data/RUBY_VERSION +1 -1
  6. data/Rakefile +6 -1
  7. data/VERSION +1 -1
  8. data/doc/Schemacop.html +32 -5
  9. data/doc/Schemacop/ArrayValidator.html +4 -4
  10. data/doc/Schemacop/BooleanValidator.html +4 -4
  11. data/doc/Schemacop/Caster.html +379 -0
  12. data/doc/Schemacop/Collector.html +298 -46
  13. data/doc/Schemacop/Exceptions.html +3 -3
  14. data/doc/Schemacop/Exceptions/InvalidSchemaError.html +3 -3
  15. data/doc/Schemacop/Exceptions/ValidationError.html +3 -3
  16. data/doc/Schemacop/FieldNode.html +19 -7
  17. data/doc/Schemacop/FloatValidator.html +4 -4
  18. data/doc/Schemacop/HashValidator.html +33 -7
  19. data/doc/Schemacop/IntegerValidator.html +4 -4
  20. data/doc/Schemacop/NilValidator.html +4 -4
  21. data/doc/Schemacop/Node.html +97 -85
  22. data/doc/Schemacop/NodeResolver.html +28 -12
  23. data/doc/Schemacop/NodeSupportingField.html +4 -4
  24. data/doc/Schemacop/NodeSupportingType.html +5 -7
  25. data/doc/Schemacop/NodeWithBlock.html +4 -4
  26. data/doc/Schemacop/NumberValidator.html +4 -4
  27. data/doc/Schemacop/ObjectValidator.html +20 -10
  28. data/doc/Schemacop/RootNode.html +4 -4
  29. data/doc/Schemacop/Schema.html +6 -6
  30. data/doc/Schemacop/StringValidator.html +3 -3
  31. data/doc/Schemacop/SymbolValidator.html +4 -4
  32. data/doc/ScopedEnv.html +3 -3
  33. data/doc/_index.html +11 -4
  34. data/doc/class_list.html +1 -1
  35. data/doc/css/style.css +10 -6
  36. data/doc/file.README.html +255 -29
  37. data/doc/frames.html +1 -1
  38. data/doc/index.html +255 -29
  39. data/doc/js/app.js +55 -0
  40. data/doc/method_list.html +99 -51
  41. data/doc/top-level-namespace.html +3 -3
  42. data/lib/schemacop.rb +15 -0
  43. data/lib/schemacop/caster.rb +38 -0
  44. data/lib/schemacop/collector.rb +53 -6
  45. data/lib/schemacop/field_node.rb +25 -3
  46. data/lib/schemacop/node.rb +16 -4
  47. data/lib/schemacop/node_resolver.rb +10 -2
  48. data/lib/schemacop/node_supporting_field.rb +0 -2
  49. data/lib/schemacop/node_supporting_type.rb +21 -1
  50. data/lib/schemacop/schema.rb +3 -3
  51. data/lib/schemacop/validator/array_validator.rb +1 -1
  52. data/lib/schemacop/validator/float_validator.rb +1 -1
  53. data/lib/schemacop/validator/hash_validator.rb +15 -2
  54. data/lib/schemacop/validator/integer_validator.rb +1 -1
  55. data/lib/schemacop/validator/object_validator.rb +7 -1
  56. data/schemacop.gemspec +15 -9
  57. data/test/casting_test.rb +90 -0
  58. data/test/collector_test.rb +45 -0
  59. data/test/custom_check_test.rb +20 -13
  60. data/test/custom_if_test.rb +12 -12
  61. data/test/defaults_test.rb +71 -0
  62. data/test/nil_dis_allow_test.rb +6 -6
  63. data/test/node_resolver_test.rb +26 -0
  64. data/test/short_forms_test.rb +86 -64
  65. data/test/test_helper.rb +7 -0
  66. data/test/types_test.rb +5 -5
  67. data/test/validator_array_test.rb +16 -16
  68. data/test/validator_boolean_test.rb +2 -2
  69. data/test/validator_float_test.rb +15 -15
  70. data/test/validator_hash_test.rb +5 -5
  71. data/test/validator_integer_test.rb +9 -9
  72. data/test/validator_nil_test.rb +1 -1
  73. data/test/validator_number_test.rb +19 -19
  74. data/test/validator_object_test.rb +33 -15
  75. data/test/validator_string_test.rb +12 -12
  76. data/test/validator_symbol_test.rb +2 -2
  77. metadata +43 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bb096c541a2c7681a8e46331b266f0e6d93e8946
4
- data.tar.gz: 2aead692c31d0eea58cc0bcc5f239cb03edcf322
2
+ SHA256:
3
+ metadata.gz: e82135ea68e1aa89e4fa729d481bcb6097705e01ba339b9692f7a69d2260f334
4
+ data.tar.gz: 26358b8124dfd17505830b7e5fb1446c37d34eb9d09533cc3b5f216d9fcccba3
5
5
  SHA512:
6
- metadata.gz: 156d68d30297a571819a9bc9c4e2ca2fe7e10bfcbe01c79d13393de506a4b27e4e6d1faf07021cda33a05ebcf7b8cefb046efcbf325e3fd248ad272271d5ce6a
7
- data.tar.gz: 7057102772faed1465753810c013b3a8ea29f0c78d03128af0f6920dce56492de7835571716ba083a4bb48c9edece13ef0f3e364b25575753937855c2209e6b1
6
+ metadata.gz: ba3a66136b5a4a756f08bf2f377621bbb047574f68e2e97f9a0d21680040330c44510e8c05b18f39da93d34a96d86a4342495d8bdcb2a0522336743a9cb19d9d
7
+ data.tar.gz: 8f0901419e3e22952ee289d47cc911f7304d5fa848456e1840255e2a5964a374ce48284927e804c530b22eb61408eb4b516dcbce8da46e969e16e504084469ef
@@ -10,6 +10,64 @@
10
10
  ### Changes
11
11
  -->
12
12
 
13
+ ## 2.4.0 (2019-10-28)
14
+
15
+ ### New features
16
+
17
+ * Add support for default values
18
+
19
+ * Add support for type casting
20
+
21
+ ### Bug fixes
22
+
23
+ * Change order of built-in validators so that `Integer` and `String` come
24
+ *before* `Number` which matches both.
25
+
26
+ ### Changes
27
+
28
+ ## 2.3.2 (2019-09-26)
29
+
30
+ ### New features
31
+
32
+ * Add ability to return custom error messages from `:check` blocks
33
+
34
+ ## 2.3.1 (2019-08-19)
35
+
36
+ ### Changes
37
+
38
+ * Make compatible with Rails 6
39
+
40
+ ## 2.3.0 (2017-05-18)
41
+
42
+ ### New features
43
+
44
+ * Option `strict` for the Type `:object`
45
+
46
+ This option, which defaults to true, ensures that instance classes are checked
47
+ strictly. If set to false, instances of derived classes are also allowed.
48
+
49
+ ### Bug fixes
50
+
51
+ * Removed '/root' from the paths in the error messages
52
+
53
+ ### Changes
54
+
55
+ * Added tests for the Collector paths to ensure correct behavior
56
+ * Added symbol Type to the short forms test
57
+
58
+ ## 2.2.0 (2017-05-17)
59
+
60
+ ### Changes
61
+
62
+ * Handle `ActiveSupport::HashWithIndifferentAccess` objects gracefully when
63
+ performing the validation. This allows the user to specify the schema using
64
+ a mixture of symbols and strings, but during the validation of a
65
+ `HashWithIndifferentAccess` it transparently converts the keys, both in the
66
+ schema and in the hash, to symbols.
67
+
68
+ In the event that a key is defined both in the string and symbol version,
69
+ Schemacop expects a Ruby hash and will throw a ValidationError otherwise.
70
+
13
71
  ## 2.1.0 (2017-05-16)
14
72
 
15
73
  ### New features
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Sitrox
3
+ Copyright (c) 2019 Sitrox
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -79,7 +79,7 @@ end
79
79
  At runtime:
80
80
 
81
81
  ```ruby
82
- my_shema.validate!(
82
+ my_schema.validate!(
83
83
  # Your data goes here
84
84
  )
85
85
  ```
@@ -108,7 +108,7 @@ this:
108
108
  s = Schema.new do
109
109
  type :integer
110
110
  type :hash do
111
- req 'name' do
111
+ req 'present' do
112
112
  type :boolean
113
113
  end
114
114
  end
@@ -124,12 +124,40 @@ We will see Type and Field lines in more detail below.
124
124
  ### `validate` vs `validate!` vs `valid?`
125
125
 
126
126
  The method `validate` will return a `Collector` object that contains all
127
- validation errors (if any), whereas `validate!` will accumulate all violations
128
- and finally throw an exception describing them.
127
+ validation errors (if any) as well as a deep copy of your data with applied
128
+ defaults and castings, whereas `validate!` will accumulate all violations
129
+ and finally throw an exception describing them or, if the validation was
130
+ successful, a deep-copy of your supplied data with defaults and castings
131
+ applied.
129
132
 
130
133
  For simply querying the validity of some data, use the methods `valid?` or
131
134
  `invalid?`.
132
135
 
136
+ Examples:
137
+
138
+ ```ruby
139
+ # validate! returns your modified data or throws a validation error
140
+ s = Schema.new do
141
+ req :foo, default: 42
142
+ end
143
+ s.validate!({}) # => { foo: 42 }
144
+
145
+ # validate returns a collector
146
+ s = Schema.new do
147
+ req :foo, default: 42
148
+ end
149
+
150
+ collector = s.validate({})
151
+ collector.valid? # true
152
+ collector.data # => { foo: 42 }
153
+
154
+ collector = s.validate({ foo: 'invalid' })
155
+ collector.valid? # false
156
+ collector.data # => nil
157
+ collector.exceptions # => Validation error
158
+ ```
159
+
160
+
133
161
  ## Schemacop's DSL
134
162
 
135
163
  In this section, we will ignore [short forms](#short-forms) and explicitly
@@ -182,7 +210,7 @@ Consider a scenario in which you want to have the following rule set:
182
210
  The corresponding schema would look as follows:
183
211
 
184
212
  ```ruby
185
- Schma.new do
213
+ Schema.new do
186
214
  type :integer, if: proc { |data| data.odd? }, max: 15
187
215
  type :integer
188
216
  end
@@ -203,8 +231,8 @@ the type checking, meaning that it only gets executed if the data has the right
203
231
  type and the proc in `if` (if any) has returned true.
204
232
 
205
233
  The proc passed to the `check` option is given the data being analyzed. It is to
206
- return true if the data passes the custom check. If it returns false, Schemacop
207
- considers the data to be invalid.
234
+ return true if the data passes the custom check. If it returns false or an error
235
+ message as a string, Schemacop considers the data to be invalid.
208
236
 
209
237
  The following example illustrates the use of the option `check`: Consider a
210
238
  scenario in which you want the following rule set:
@@ -224,10 +252,21 @@ end
224
252
  The above Type Line has type `:string` and two options (`min` and `check`). The
225
253
  option `min` is supported by the `:string` validator (covered later).
226
254
 
255
+ You can also specify a custom error message by returning a string:
256
+
257
+
258
+ ```ruby
259
+ Schema.new do
260
+ type :integer, check: proc { |i| i.even? ? true : 'Custom error' }
261
+ end
262
+ ```
263
+
264
+ This will include `Custom error` in the validation error message.
265
+
227
266
  ### Field Line
228
267
 
229
- Inside a Type Line of type `:hash` or `Hash`, you may specify an arbitrary
230
- number of field lines (one for each key-value pair you want to be in the hash).
268
+ Inside a Type Line of type `:hash`, you may specify an arbitrary number of field
269
+ lines (one for each key-value pair you want to be in the hash).
231
270
 
232
271
  Field Lines start with one of the following six identifiers: `req`, `req?`,
233
272
  `req!`, `opt`, `opt?` or `opt!`:
@@ -273,6 +312,36 @@ end
273
312
  You might find the notation cumbersome, and you'd be right to say so. Luckily
274
313
  there are plenty of short forms available which we will see below.
275
314
 
315
+ #### Handling hashes with indifferent access
316
+
317
+ Schemacop has special handling for objects of the class
318
+ `ActiveSupport::HashWithIndifferentAccess`: You may specify the keys as symbols
319
+ or strings, and Schemacop will handle the conversion necessary for proper
320
+ validation internally. Note that if you define the same key as string and
321
+ symbol, it will throw a `ValidationError` [exception](#exceptions) when asked to
322
+ validate a hash with indifferent access.
323
+
324
+ Thus, the following two schema definitions are equivalent when validating a hash
325
+ with indifferent access:
326
+
327
+ ```ruby
328
+ Schema.new do
329
+ type :hash do
330
+ req :name do
331
+ type :string
332
+ end
333
+ end
334
+ end
335
+
336
+ Schema.new do
337
+ type :hash do
338
+ req 'name' do
339
+ type :string
340
+ end
341
+ end
342
+ end
343
+ ```
344
+
276
345
  ## Types
277
346
 
278
347
  Types are defined via their validators, which is a class under `validator/`.
@@ -302,11 +371,16 @@ The following types are supported by Schemacop by default:
302
371
 
303
372
  * `:object` accepts an arbitrary Ruby object (any object if no option is given).
304
373
 
305
- - supported option: `classes`: Ruby class (or an array of them) that will be
306
- the only recognized filters. Unlike other options, this one affects not the
307
- validation but the type recognition, meaning that you can have multiple Type
308
- Lines with different `classes` option for the same field, each having its
309
- own validation (e.g. through the option `check`).
374
+ Supported options:
375
+
376
+ - `classes`: Ruby class (or an array of them) that will be the only recognized
377
+ filters. Unlike other options, this one affects not the validation but the
378
+ type recognition, meaning that you can have multiple Type Lines with
379
+ different `classes` option for the same field, each having its own
380
+ validation (e.g. through the option `check`).
381
+
382
+ - `strict`: Boolean option, defaults to true. If set to false, the validator
383
+ also allows derived classes of those specified with `classes`.
310
384
 
311
385
  * `:array` accepts a Ruby Array.
312
386
 
@@ -317,7 +391,7 @@ The following types are supported by Schemacop by default:
317
391
  - TODO no lookahead for different arrays, see
318
392
  validator_array_test#test_multiple_arrays
319
393
 
320
- * `:hash` accepts a Ruby Hash.
394
+ * `:hash` accepts a Ruby Hash or an `ActiveSupport::HashWithIndifferentAccess`.
321
395
 
322
396
  - accepts a block with an arbitrary number of Field Lines.
323
397
 
@@ -443,6 +517,10 @@ Schema.new do
443
517
  end
444
518
  ```
445
519
 
520
+ Note that this does not allow you to specify any options for the hash itself.
521
+ You still need to specify `:hash` as a type if you want to pass any options to
522
+ the hash (i.e. a `default`).
523
+
446
524
  ### Shortform for subtypes
447
525
 
448
526
  In case of nested arrays, you can group all Type Lines to a single one.
@@ -501,6 +579,149 @@ of type Array with children of type Array with children of type Hash in which at
501
579
  least one of the Symbol keys `:food` and `:drink` (with any non-nil value type)
502
580
  is present.
503
581
 
582
+ ## Defaults
583
+
584
+ Starting from version 2.4.0, Schemacop allows you to define default values at
585
+ any point in your schema. If the validated data contains a nil value, it will be
586
+ substituted by the given default value.
587
+
588
+ Note that Schemacop never modifies the data you pass to it. If you want to
589
+ benefit from Schemacop-applied defaults, you need to access the cloned, modified
590
+ data returned by `validate` or `validate!`.
591
+
592
+ Applying defaults is done before validating the substructure and before any type
593
+ casting. The provided default will be validated same as user-supplied data, so
594
+ if your given default does not validate properly, a validation error is thrown.
595
+ Make sure your default values always match the underlying schema.
596
+
597
+ Defaults can be specified at any point:
598
+
599
+
600
+ ```ruby
601
+ # Basic usage
602
+ Schema.new do
603
+ type :string, default: 'Hello World'
604
+ end
605
+
606
+ # The default given for the first type will match
607
+ Schema.new do
608
+ type :string, default: 'Hello World' # This will always be applied of no value is supplied
609
+ type :integer, default: 42
610
+ end
611
+
612
+ # You can also pass entire hashes or arrays to your defaults
613
+ Schema.new do
614
+ req :foo, :hash, default: { foo: :bar } do
615
+ req :foo, :symbol
616
+ end
617
+ req :bar, :array, :integer, default: [1, 2, 3]
618
+ end
619
+
620
+ # Defaults must match the given schema. The following will fail.
621
+ Schema.new do
622
+ req :foo, default: { bar: :baz } do
623
+ req :foo
624
+ end
625
+ end
626
+ ```
627
+
628
+ ### Required data points
629
+
630
+ Note that any *required* validation is done before applying the defaults. If you
631
+ specify a `req` field, it must always be given, no matter if you have specified
632
+ a default or not. Therefore, specifying `req` fields do not make sense in
633
+ conjunction with defaults, as the default is always ignored.
634
+
635
+ ## Type casting
636
+
637
+ Starting from version 2.4.0, Schemacop allows you to specify type castings that
638
+ can alter the validated data. Consider the following:
639
+
640
+ ```ruby
641
+ s = Schema.new do
642
+ req :id, :integer, cast: [String]
643
+ end
644
+
645
+ data = s.validate!(id: '42')
646
+ data # => { id: 42 }
647
+ ```
648
+
649
+ Note that Schemacop never modifies the data you pass to it. If you want to
650
+ benefit from Schemacop-applied castings, you need to access the cloned, modified
651
+ data returned by `validate` or `validate!`.
652
+
653
+ ### Specifying type castings
654
+
655
+ Type castings can be specified using two forms: Either as a hash or as an array.
656
+ While using an array only allows you to specify the supported source types to be
657
+ casted, using a hash allows you to specify custom casting logic as blocks.
658
+
659
+ For hashes, the key must be a class and the value must be either `:default` for
660
+ using a built-in caster or a callable object (proc or lambda) that receives the
661
+ value and is supposed to cast it. If the value can't be casted, the proc must
662
+ fail with an exception. The exception message will then be contained in the
663
+ collected validation errors.
664
+
665
+ Example:
666
+
667
+ ```ruby
668
+ Schema.new do
669
+ # Pass array to `cast`. This enables casting from String or Float to Integer
670
+ # using the built-in casters.
671
+ req: id_1, :integer, cast: [String, Float]
672
+
673
+ # Pass hash to `cast`. This enables casting from Float to Integer using the
674
+ # built-in caster and from String to Integer using a custom callback.
675
+ req :id_2, :integer, cast: { Float => :default, String => proc { |s| Integer(s) }
676
+ end
677
+ ```
678
+
679
+ ### Built-in casters
680
+
681
+ Schemacop comes with the following casters:
682
+
683
+ - `String` to `Integer` and `Float`
684
+ - `Float` to `Integer`
685
+ - `Integer` to `Float`
686
+
687
+ Note that all built-in casters are precise, so the string `foo` will fail with
688
+ an error if casted to an Integer. When casting float values and strings
689
+ containing float values to integers, the decimal places will be discarded
690
+ however.
691
+
692
+ ### Execution order
693
+
694
+ The casting is done *before* the options `if` and `check` are evaluated.
695
+ Example:
696
+
697
+ ```ruby
698
+ s = Schema.new do
699
+ type :integer, if: proc { |i| i == 42 } # 1
700
+ type :integer, check: proc { |i| i < 3 } # 2
701
+ type :string
702
+ end
703
+
704
+ s.validate!('42') # 1 will match
705
+ s.validate!('2') # 2 will match
706
+ s.validate!('234') # 3 will match
707
+ s.validate!(5) # Will fail, as nothing matches
708
+ ```
709
+
710
+ ### Caveats
711
+
712
+ Casting only works with type definitions that only include one type. For
713
+ instance, the `Numeric` validator includes both `Integer` and `Float`, which
714
+ would made it unclear what to cast a string into:
715
+
716
+ ```ruby
717
+ # This does not work, as it is unclear whether to cast the String into an
718
+ # Integer or a Float.
719
+ type :number, cast: [String]
720
+ ```
721
+
722
+ The same also applies to booleans, as they compound both `TrueClass` and
723
+ `FalseClass`. This may be tackled in future releases.
724
+
504
725
  ## Exceptions
505
726
 
506
727
  Schemacop will throw one of the following checked exceptions:
@@ -526,6 +747,19 @@ Schemacop will throw one of the following checked exceptions:
526
747
 
527
748
  * Schemacop does not yet support string regex matching.
528
749
 
750
+ ## Development
751
+
752
+ To run tests:
753
+
754
+ * Check out the source
755
+
756
+ * Run `bundle install`
757
+
758
+ * Run `bundle exec rake test` to run all tests
759
+
760
+ * Run `bundle exec rake test TEST=test/unit/some/file.rb` to run a single test
761
+ file
762
+
529
763
  ## Contributors
530
764
 
531
765
  Thanks to [Rubocop](https://github.com/bbatsov/rubocop) for great inspiration
@@ -534,4 +768,4 @@ to [SubGit](http://www.subgit.com/) for their great open source licensing.
534
768
 
535
769
  ## Copyright
536
770
 
537
- Copyright (c) 2017 Sitrox. See `LICENSE` for further details.
771
+ Copyright (c) 2019 Sitrox. See `LICENSE` for further details.
@@ -1 +1 @@
1
- ruby-2.3.1-p112
1
+ ruby-2.6.2-p47
data/Rakefile CHANGED
@@ -14,15 +14,20 @@ task :gemspec do
14
14
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
15
  spec.require_paths = ['lib']
16
16
 
17
+ # This lower bound for ActiveSupport is not necessarily true. Schemacop
18
+ # needs access to ActiveSupport::HashWithIndifferentAccess and expects
19
+ # behavior of that as in version 5 of ActiveSupport.
20
+ spec.add_dependency 'activesupport', '>= 4.0'
17
21
  spec.add_development_dependency 'bundler', '~> 1.3'
18
22
  spec.add_development_dependency 'rake'
19
23
  spec.add_development_dependency 'ci_reporter', '~> 2.0'
20
24
  spec.add_development_dependency 'ci_reporter_minitest'
21
- spec.add_development_dependency 'activesupport'
22
25
  spec.add_development_dependency 'haml'
26
+ spec.add_development_dependency 'colorize'
23
27
  spec.add_development_dependency 'yard'
24
28
  spec.add_development_dependency 'rubocop', '0.35.1'
25
29
  spec.add_development_dependency 'redcarpet'
30
+ spec.add_development_dependency 'pry'
26
31
  end
27
32
 
28
33
  File.open('schemacop.gemspec', 'w') { |f| f.write(gemspec.to_ruby.strip) }