schemacop 2.4.4 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +25 -1
  4. data/.travis.yml +3 -1
  5. data/CHANGELOG.md +21 -0
  6. data/README.md +53 -698
  7. data/README_V2.md +775 -0
  8. data/README_V3.md +1051 -0
  9. data/Rakefile +8 -12
  10. data/VERSION +1 -1
  11. data/lib/schemacop.rb +35 -36
  12. data/lib/schemacop/base_schema.rb +37 -0
  13. data/lib/schemacop/railtie.rb +10 -0
  14. data/lib/schemacop/schema.rb +1 -60
  15. data/lib/schemacop/schema2.rb +22 -0
  16. data/lib/schemacop/schema3.rb +21 -0
  17. data/lib/schemacop/scoped_env.rb +25 -13
  18. data/lib/schemacop/v2.rb +26 -0
  19. data/lib/schemacop/{caster.rb → v2/caster.rb} +16 -2
  20. data/lib/schemacop/{collector.rb → v2/collector.rb} +5 -2
  21. data/lib/schemacop/{dupper.rb → v2/dupper.rb} +1 -1
  22. data/lib/schemacop/{field_node.rb → v2/field_node.rb} +5 -3
  23. data/lib/schemacop/v2/node.rb +142 -0
  24. data/lib/schemacop/{node_resolver.rb → v2/node_resolver.rb} +1 -1
  25. data/lib/schemacop/v2/node_supporting_field.rb +70 -0
  26. data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +14 -11
  27. data/lib/schemacop/{node_with_block.rb → v2/node_with_block.rb} +3 -2
  28. data/lib/schemacop/v2/root_node.rb +6 -0
  29. data/lib/schemacop/v2/validator/array_validator.rb +32 -0
  30. data/lib/schemacop/{validator → v2/validator}/boolean_validator.rb +1 -1
  31. data/lib/schemacop/v2/validator/float_validator.rb +7 -0
  32. data/lib/schemacop/v2/validator/hash_validator.rb +37 -0
  33. data/lib/schemacop/v2/validator/integer_validator.rb +7 -0
  34. data/lib/schemacop/{validator → v2/validator}/nil_validator.rb +1 -1
  35. data/lib/schemacop/v2/validator/number_validator.rb +21 -0
  36. data/lib/schemacop/v2/validator/object_validator.rb +29 -0
  37. data/lib/schemacop/v2/validator/string_validator.rb +39 -0
  38. data/lib/schemacop/{validator → v2/validator}/symbol_validator.rb +1 -1
  39. data/lib/schemacop/v3.rb +45 -0
  40. data/lib/schemacop/v3/all_of_node.rb +27 -0
  41. data/lib/schemacop/v3/any_of_node.rb +28 -0
  42. data/lib/schemacop/v3/array_node.rb +219 -0
  43. data/lib/schemacop/v3/boolean_node.rb +16 -0
  44. data/lib/schemacop/v3/combination_node.rb +45 -0
  45. data/lib/schemacop/v3/context.rb +17 -0
  46. data/lib/schemacop/v3/dsl_scope.rb +46 -0
  47. data/lib/schemacop/v3/global_context.rb +114 -0
  48. data/lib/schemacop/v3/hash_node.rb +238 -0
  49. data/lib/schemacop/v3/integer_node.rb +13 -0
  50. data/lib/schemacop/v3/is_not_node.rb +32 -0
  51. data/lib/schemacop/v3/node.rb +214 -0
  52. data/lib/schemacop/v3/node_registry.rb +49 -0
  53. data/lib/schemacop/v3/number_node.rb +18 -0
  54. data/lib/schemacop/v3/numeric_node.rb +76 -0
  55. data/lib/schemacop/v3/object_node.rb +40 -0
  56. data/lib/schemacop/v3/one_of_node.rb +28 -0
  57. data/lib/schemacop/v3/reference_node.rb +49 -0
  58. data/lib/schemacop/v3/result.rb +58 -0
  59. data/lib/schemacop/v3/string_node.rb +124 -0
  60. data/lib/schemacop/v3/symbol_node.rb +13 -0
  61. data/schemacop.gemspec +24 -27
  62. data/test/lib/test_helper.rb +152 -0
  63. data/test/schemas/nested/group.rb +6 -0
  64. data/test/schemas/user.rb +7 -0
  65. data/test/unit/schemacop/v2/casting_test.rb +120 -0
  66. data/test/unit/schemacop/v2/collector_test.rb +47 -0
  67. data/test/unit/schemacop/v2/custom_check_test.rb +95 -0
  68. data/test/unit/schemacop/v2/custom_if_test.rb +97 -0
  69. data/test/unit/schemacop/v2/defaults_test.rb +95 -0
  70. data/test/unit/schemacop/v2/empty_test.rb +16 -0
  71. data/test/unit/schemacop/v2/nil_dis_allow_test.rb +43 -0
  72. data/test/unit/schemacop/v2/node_resolver_test.rb +28 -0
  73. data/test/unit/schemacop/v2/short_forms_test.rb +351 -0
  74. data/test/unit/schemacop/v2/types_test.rb +88 -0
  75. data/test/unit/schemacop/v2/validator_array_test.rb +99 -0
  76. data/test/unit/schemacop/v2/validator_boolean_test.rb +17 -0
  77. data/test/unit/schemacop/v2/validator_float_test.rb +59 -0
  78. data/test/unit/schemacop/v2/validator_hash_test.rb +95 -0
  79. data/test/unit/schemacop/v2/validator_integer_test.rb +48 -0
  80. data/test/unit/schemacop/v2/validator_nil_test.rb +15 -0
  81. data/test/unit/schemacop/v2/validator_number_test.rb +62 -0
  82. data/test/unit/schemacop/v2/validator_object_test.rb +141 -0
  83. data/test/unit/schemacop/v2/validator_string_test.rb +78 -0
  84. data/test/unit/schemacop/v2/validator_symbol_test.rb +18 -0
  85. data/test/unit/schemacop/v3/all_of_node_test.rb +199 -0
  86. data/test/unit/schemacop/v3/any_of_node_test.rb +218 -0
  87. data/test/unit/schemacop/v3/array_node_test.rb +805 -0
  88. data/test/unit/schemacop/v3/boolean_node_test.rb +126 -0
  89. data/test/unit/schemacop/v3/global_context_test.rb +164 -0
  90. data/test/unit/schemacop/v3/hash_node_test.rb +826 -0
  91. data/test/unit/schemacop/v3/integer_node_test.rb +323 -0
  92. data/test/unit/schemacop/v3/is_not_node_test.rb +173 -0
  93. data/test/unit/schemacop/v3/node_test.rb +148 -0
  94. data/test/unit/schemacop/v3/number_node_test.rb +292 -0
  95. data/test/unit/schemacop/v3/object_node_test.rb +170 -0
  96. data/test/unit/schemacop/v3/one_of_node_test.rb +187 -0
  97. data/test/unit/schemacop/v3/reference_node_test.rb +351 -0
  98. data/test/unit/schemacop/v3/string_node_test.rb +334 -0
  99. data/test/unit/schemacop/v3/symbol_node_test.rb +75 -0
  100. metadata +152 -143
  101. data/doc/Schemacop.html +0 -146
  102. data/doc/Schemacop/ArrayValidator.html +0 -329
  103. data/doc/Schemacop/BooleanValidator.html +0 -145
  104. data/doc/Schemacop/Caster.html +0 -379
  105. data/doc/Schemacop/Collector.html +0 -787
  106. data/doc/Schemacop/Dupper.html +0 -214
  107. data/doc/Schemacop/Exceptions.html +0 -115
  108. data/doc/Schemacop/Exceptions/InvalidSchemaError.html +0 -124
  109. data/doc/Schemacop/Exceptions/ValidationError.html +0 -124
  110. data/doc/Schemacop/FieldNode.html +0 -421
  111. data/doc/Schemacop/FloatValidator.html +0 -158
  112. data/doc/Schemacop/HashValidator.html +0 -293
  113. data/doc/Schemacop/IntegerValidator.html +0 -158
  114. data/doc/Schemacop/NilValidator.html +0 -145
  115. data/doc/Schemacop/Node.html +0 -1438
  116. data/doc/Schemacop/NodeResolver.html +0 -258
  117. data/doc/Schemacop/NodeSupportingField.html +0 -590
  118. data/doc/Schemacop/NodeSupportingType.html +0 -612
  119. data/doc/Schemacop/NodeWithBlock.html +0 -289
  120. data/doc/Schemacop/NumberValidator.html +0 -232
  121. data/doc/Schemacop/ObjectValidator.html +0 -298
  122. data/doc/Schemacop/RootNode.html +0 -171
  123. data/doc/Schemacop/Schema.html +0 -699
  124. data/doc/Schemacop/StringValidator.html +0 -295
  125. data/doc/Schemacop/SymbolValidator.html +0 -145
  126. data/doc/ScopedEnv.html +0 -351
  127. data/doc/_index.html +0 -379
  128. data/doc/class_list.html +0 -51
  129. data/doc/css/common.css +0 -1
  130. data/doc/css/full_list.css +0 -58
  131. data/doc/css/style.css +0 -496
  132. data/doc/file.README.html +0 -821
  133. data/doc/file_list.html +0 -56
  134. data/doc/frames.html +0 -17
  135. data/doc/index.html +0 -821
  136. data/doc/inheritance.graphml +0 -524
  137. data/doc/inheritance.pdf +0 -825
  138. data/doc/js/app.js +0 -303
  139. data/doc/js/full_list.js +0 -216
  140. data/doc/js/jquery.js +0 -4
  141. data/doc/method_list.html +0 -587
  142. data/doc/top-level-namespace.html +0 -112
  143. data/lib/schemacop/node.rb +0 -139
  144. data/lib/schemacop/node_supporting_field.rb +0 -58
  145. data/lib/schemacop/root_node.rb +0 -4
  146. data/lib/schemacop/validator/array_validator.rb +0 -30
  147. data/lib/schemacop/validator/float_validator.rb +0 -5
  148. data/lib/schemacop/validator/hash_validator.rb +0 -35
  149. data/lib/schemacop/validator/integer_validator.rb +0 -5
  150. data/lib/schemacop/validator/number_validator.rb +0 -19
  151. data/lib/schemacop/validator/object_validator.rb +0 -27
  152. data/lib/schemacop/validator/string_validator.rb +0 -37
  153. data/test/casting_test.rb +0 -90
  154. data/test/collector_test.rb +0 -45
  155. data/test/custom_check_test.rb +0 -93
  156. data/test/custom_if_test.rb +0 -95
  157. data/test/defaults_test.rb +0 -71
  158. data/test/nil_dis_allow_test.rb +0 -41
  159. data/test/node_resolver_test.rb +0 -26
  160. data/test/short_forms_test.rb +0 -349
  161. data/test/test_helper.rb +0 -13
  162. data/test/types_test.rb +0 -84
  163. data/test/validator_array_test.rb +0 -97
  164. data/test/validator_boolean_test.rb +0 -15
  165. data/test/validator_float_test.rb +0 -57
  166. data/test/validator_hash_test.rb +0 -93
  167. data/test/validator_integer_test.rb +0 -46
  168. data/test/validator_nil_test.rb +0 -13
  169. data/test/validator_number_test.rb +0 -60
  170. data/test/validator_object_test.rb +0 -139
  171. data/test/validator_string_test.rb +0 -76
  172. data/test/validator_symbol_test.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 563c4f0b6060acb996be2d7ee5b9cd74e09be02a3006792c1b00de306cb2b33e
4
- data.tar.gz: 289cfade30ae18f3fbb7a722efe21be5121db73a91cc28e05e9809f47166eff0
3
+ metadata.gz: 5caf1b6b11b5c690dc05694cab67e34fb6ecc326381f9d7b63b9c016b5249d9c
4
+ data.tar.gz: 8b819ad6e32dd2329bd20c0869c216c677b766a5d7374a49f6a9c146d0e743d5
5
5
  SHA512:
6
- metadata.gz: 6d6756524121f90c21388288725453c79c0a1130bceb491ab57bd2b9f8cddc7ead0ee55fa3c33566af092d47f16e137394eabe0c8e1d8f76504ddc534c14ece7
7
- data.tar.gz: 2248c109c238409770c16411e669be158e293d8e0cb446c1649186f5e90f053b8cbee1ef6cf3ca0b429ac4c1f12a3c1c83460e8a799c2683a98ad09c8ed5d90a
6
+ metadata.gz: 5db8ca31cdf29cea9744529fec11af70dc1c534dc970639931e3a9c287f3005cc578e2d7e9b0525ceefdbcb17d5f01d46d42c0a5b5868b62cabafe2779464ccc
7
+ data.tar.gz: a12d18800342da6d1d9fdc39bffbec18877872c0cffb62c9b4a41cbd8954b0915c68afeb0b9e03c7e3bdd21f5803fa57daa7deb9cfd49a14363b67586e4e2fc0
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ tmtags
15
15
  .idea
16
16
  /*.gem
17
17
  tags
18
+ .byebug_history
19
+
20
+ /coverage
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ NewCops: enable
2
3
  Exclude:
3
4
  - 'local/**/*'
4
5
  - 'vendor/**/*'
@@ -24,6 +25,9 @@ Metrics/ClassLength:
24
25
  Metrics/ModuleLength:
25
26
  Enabled: false
26
27
 
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+
27
31
  Metrics/ParameterLists:
28
32
  Max: 5
29
33
  CountKeywordArgs: false
@@ -37,7 +41,7 @@ Metrics/CyclomaticComplexity:
37
41
  Metrics/PerceivedComplexity:
38
42
  Enabled: False
39
43
 
40
- Metrics/LineLength:
44
+ Layout/LineLength:
41
45
  Max: 160
42
46
 
43
47
  Metrics/BlockNesting:
@@ -82,3 +86,23 @@ Style/ClassAndModuleChildren:
82
86
 
83
87
  Style/FormatString:
84
88
  Enabled: false
89
+
90
+ # Multiline hashes should be aligned cleanly as a table to improve readability.
91
+ Layout/HashAlignment:
92
+ EnforcedHashRocketStyle: table
93
+ EnforcedColonStyle: table
94
+
95
+ Style/FrozenStringLiteralComment:
96
+ Enabled: false
97
+
98
+ Style/AccessorGrouping:
99
+ Enabled: false
100
+
101
+ Style/DoubleNegation:
102
+ Enabled: false
103
+
104
+ Style/ConditionalAssignment:
105
+ Enabled: false
106
+
107
+ Style/CaseLikeIf:
108
+ Enabled: false
@@ -1,6 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.0
3
+ - 2.6.2
4
+ - 2.7.1
5
+ - 3.0.0
4
6
  script:
5
7
  - bundle install
6
8
  - bundle exec rake test
@@ -10,6 +10,27 @@
10
10
  ### Changes
11
11
  -->
12
12
 
13
+ ## 3.0.0.rc0
14
+
15
+ * Add `Schemacop::Schema3`
16
+
17
+ * Adapt Readme for Version 2 and 3 of `Schemacop`
18
+
19
+ * Add `ruby-2.7.1` to travis testing
20
+
21
+ ## 2.4.7 (2020-07-02)
22
+
23
+ * Return nil when casting an empty string to an Integer or a Float,
24
+ such that these cases can then be handled by the `opt` or `req`.
25
+
26
+ ## 2.4.6 (2020-06-29)
27
+
28
+ * Use basis 10 (decimal system) when casting a `String` to an `Integer`
29
+
30
+ ## 2.4.5 (2020-05-13)
31
+
32
+ * Allow procs for `default` that will be evaluated at runtime
33
+
13
34
  ## 2.4.4 (2020-03-9)
14
35
 
15
36
  * Add option `allow_obsolete_keys` to `:hash` validator in order to allow
data/README.md CHANGED
@@ -3,49 +3,45 @@
3
3
 
4
4
  # Schemacop
5
5
 
6
- This is the README for Schemacop version 2, which **breaks backwards
7
- compatibility** with version 1.
8
-
9
6
  Schemacop validates ruby structures consisting of nested hashes and arrays
10
- against schema definitions described by a simple DSL.
7
+ against schema definitions described by a simple DSL. It is also able to
8
+ generate [JSON Schema](https://json-schema.org) compliant JSON output, i.e. for
9
+ use in conjunction with [OpenAPI](https://swagger.io/specification/).
10
+
11
+ ## Compatibility
12
+
13
+ Schemacop is tested with the following ruby versions:
14
+
15
+ * 2.6.2
16
+ * 2.7.1
17
+ * 3.0.0
18
+
19
+ For these versions, the automated CI tests are ran on travis. Other ruby versions might work, but stick to these versions for best results.
11
20
 
12
- Examples:
21
+ ## Basic example
13
22
 
14
23
  ```ruby
15
- schema = Schema.new do
16
- req :naming, :hash do
17
- opt :first_name, :string
18
- req :last_name, :string
24
+ schema = Schemacop::Schema3.new :hash do
25
+ scm :group do
26
+ str! :name
19
27
  end
20
- opt! :age, :integer, min: 18
21
- req? :password do
22
- type :string, check: proc { |pw| pw.include?('*') }
23
- type :integer
28
+ str! :name
29
+ int? :age, minimum: 21
30
+ ary! :groups do
31
+ list :reference, path: :group
24
32
  end
25
33
  end
26
34
 
27
35
  schema.validate!(
28
- naming: { first_name: 'John',
29
- last_name: 'Doe' },
30
- age: 34,
31
- password: 'my*pass'
36
+ name: 'John Doe',
37
+ age: 42,
38
+ groups: [
39
+ { name: 'Group 1' },
40
+ { name: 'Group 2' }
41
+ ]
32
42
  )
33
43
  ```
34
44
 
35
- ```ruby
36
- schema2 = Schema.new do
37
- req :description,
38
- :string,
39
- if: proc { |str| str.start_with?('Abstract: ') },
40
- max: 35,
41
- check: proc { |str| !str.end_with?('.') }
42
- req :description, :string, min: 35
43
- end
44
-
45
- schema2.validate!(description: 'Abstract: a short description')
46
- schema2.validate!(description: 'Since this is no abstract, we expect it to be longer.')
47
- ```
48
-
49
45
  ## Installation
50
46
 
51
47
  To install the **Schemacop** gem:
@@ -58,696 +54,55 @@ To install it using `bundler` (recommended for any application), add it to your
58
54
  `Gemfile`:
59
55
 
60
56
  ```ruby
61
- gem 'schemacop'
62
- ```
63
-
64
- ## Basics
65
-
66
- Since there is no explicit typing in Ruby, it can be hard to make sure that a
67
- method is recieving exactly the right kind of data it needs. The idea of this
68
- gem is to define a schema at boot time that will validate the data being passed
69
- around at runtime. Those two steps look as follows:
70
-
71
- At boot time:
72
-
73
- ```ruby
74
- my_schema = Schema.new do
75
- # Your specification goes here
76
- end
77
- ```
78
-
79
- At runtime:
80
-
81
- ```ruby
82
- my_schema.validate!(
83
- # Your data goes here
84
- )
85
- ```
86
-
87
- `validate!` will fail if the data given to it does not match what was specified
88
- in the schema.
89
-
90
- ### Type lines vs. Field lines
91
-
92
- Schemacop uses a DSL (domain-specific language) to let you describe your
93
- schemas. We distinguish between two kinds of identifiers:
94
-
95
- - Field Lines: We call a key-value pair (like the contents of a hash) a *field*.
96
- A field line typically starts with the keyword `req` (for a required field) or
97
- `opt` (for an optional field).
98
-
99
- - Type Lines: Those start with the keyword `type` and specify the data type to
100
- be accepted with a corresponding symbol (e.g. `:integer` or `:boolean`). You
101
- can have multiple Type Lines for a Field Line in order to indicate that the
102
- field's value can be of one of the specified types.
103
-
104
- If you don't use any short forms, a schema definition would be something like
105
- this:
106
-
107
- ```ruby
108
- s = Schema.new do
109
- type :integer
110
- type :hash do
111
- req 'present' do
112
- type :boolean
113
- end
114
- end
115
- end
116
- ```
117
-
118
- The above schema would accept either an integer or a hash with exactly one field
119
- with key 'present' of type String and value of type Boolean (either TrueClass or
120
- FalseClass).
121
-
122
- We will see Type and Field lines in more detail below.
123
-
124
- ### `validate` vs `validate!` vs `valid?`
125
-
126
- The method `validate` will return a `Collector` object that contains all
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.
132
-
133
- For simply querying the validity of some data, use the methods `valid?` or
134
- `invalid?`.
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
-
161
- ## Schemacop's DSL
162
-
163
- In this section, we will ignore [short forms](#short-forms) and explicitly
164
- write out everything.
165
-
166
- Inside the block given at the schema instantiation (`Schema.new do ... end`),
167
- the following kinds of method calls are allowed (where the outermost must be a
168
- Type Line):
169
-
170
- ### Type Line
171
-
172
- A Type Line always starts with the identifier `type` and specifies a possible
173
- data type for a given field (if inside a Field Line) or the given data structure
174
- (if directly below the schema instantiation).
175
-
176
- Type Lines are generally of the form
177
-
178
- ```ruby
179
- type :my_type, option_1: value_1, ..., option_n: value_n
180
- ```
181
-
182
- where `:my_type` is a supported symbol (see section [Types](#types) below for
183
- supported types).
184
-
185
- #### General options
186
-
187
- Some types support specific options that allow additional checks on the nature
188
- of the data (such as the `min` option for type `:number`). The following options
189
- are supported by all types:
190
-
191
- ##### Option `if`
192
-
193
- This option takes a proc (or a lambda) as value. The proc will be called when
194
- checking whether or not the data being analyzed fits a certain type. The data is
195
- given to the proc, which has to return either true or false. If it returns true,
196
- the type of the given data is considered correct and the data will be validated
197
- if further options are given.
198
-
199
- Note that the proc in `if` will only get called if the type (`:my_type` from
200
- above) fits the data already. You can use the option `if` in order to say: "Even
201
- if the data is of type `:my_type`, I consider it having the wrong type if my
202
- proc returns false."
203
-
204
- Consider a scenario in which you want to have the following rule set:
205
-
206
- - Only integers may be given
207
- - Odd integers must be no larger than 15
208
- - No limitations for even integers
209
-
210
- The corresponding schema would look as follows:
211
-
212
- ```ruby
213
- Schema.new do
214
- type :integer, if: proc { |data| data.odd? }, max: 15
215
- type :integer
216
- end
57
+ gem 'schemacop', '>= 3.0.0'
217
58
  ```
218
59
 
219
- Here, the first type line will only accept odd numbers and the option `max: 15`
220
- provided by the `:integer` validator will discard numbers higher than 15.
221
-
222
- Since the first line only accepts odd numbers, it doesn't apply for even numbers
223
- (due to the proc given to `if` they are considered to be of the wrong type) and
224
- control falls through to the second type line accepting any integer.
225
-
226
- ##### Option `check`
60
+ ## Schema specification
227
61
 
228
- This option allows you to perform arbitrary custom checks for a given data type.
229
- Just like `if`, `check` takes a proc or lambda as a value, but it runs *after*
230
- the type checking, meaning that it only gets executed if the data has the right
231
- type and the proc in `if` (if any) has returned true.
62
+ The actual schema definition depends on the schema version you're using.
63
+ Schemacop 3 supports version 3 and also the legacy version 2 for backwards
64
+ compatibility. For version 1, you need to use the `1.x` versions of schemacop.
232
65
 
233
- The proc passed to the `check` option is given the data being analyzed. It is to
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.
66
+ * [Schema version 3](README_V3.md)
67
+ * [Schema version 2](README_V2.md) (legacy)
236
68
 
237
- The following example illustrates the use of the option `check`: Consider a
238
- scenario in which you want the following rule set:
69
+ ## JSON generation
239
70
 
240
- - Data must be of type String
241
- - The string must be longer than 5 characters
242
- - The second character must be an 'r'
243
-
244
- The corresponding schema would look as follows:
71
+ Using the method `as_json` on any V3 schema will produce a JSON schema compliant
72
+ to the JSON Schema standard.
245
73
 
246
74
  ```ruby
247
- Schema.new do
248
- type :string, min: 5, check: proc { |data| data[1] == 'r'}
249
- end
250
- ```
251
-
252
- The above Type Line has type `:string` and two options (`min` and `check`). The
253
- option `min` is supported by the `:string` validator (covered later).
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
-
266
- ### Field Line
75
+ Schemacop::Schema3.new :hash do
76
+ str! :name
77
+ end.as_json
267
78
 
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).
270
-
271
- Field Lines start with one of the following six identifiers: `req`, `req?`,
272
- `req!`, `opt`, `opt?` or `opt!`:
273
-
274
- - The suffix `-!` means that the field must not be nil.
275
-
276
- - The suffix `-?` means that the field may be nil.
277
-
278
- - The prefix `req-` denotes a required field (validation fails if the given data
279
- hash doesn't define it). `req` is a shorthand notation for `req!` (meaning
280
- that by default, a required field cannot be nil).
281
-
282
- - The prefix `opt-` denotes an optional field. `opt` is a shorthand notation for
283
- `opt?` (meaning that by default, an optional field may be nil).
284
-
285
- To summarize:
286
-
287
- - `req` or `req!`: required and non-nil
288
- - `req?`: required but may be nil
289
- - `opt` or `opt?`: optional and may be nil
290
- - `opt!`: optional but non-nil
291
-
292
- You then pass a block with a single or multiple Type Lines to the field.
293
-
294
- Example: The following schema defines a hash that has a required non-nil field
295
- of type String under the key `:name` (of type Symbol) and an optional but
296
- non-nil field of type Integer or Date under the key `:age`.
297
-
298
- ```ruby
299
- Schema.new do
300
- type :hash do
301
- req :name do
302
- type :string
303
- end
304
- opt! :age do
305
- type :integer
306
- type :object, classes: Date
307
- end
308
- end
309
- end
79
+ # Will result in
80
+ {
81
+ type: :object,
82
+ properties: {
83
+ name: { type: :string }
84
+ },
85
+ additionalProperties: false,
86
+ required: [:name]
87
+ }
310
88
  ```
311
89
 
312
- You might find the notation cumbersome, and you'd be right to say so. Luckily
313
- there are plenty of short forms available which we will see below.
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
-
345
- ## Types
346
-
347
- Types are defined via their validators, which is a class under `validator/`.
348
- Each validator is sourced by `schemacop.rb`.
349
-
350
- The following types are supported by Schemacop by default:
351
-
352
- * `:boolean` accepts a Ruby TrueClass or FalseClass instance.
353
-
354
- * `:integer` accepts a Ruby Integer.
355
-
356
- - supported options: `min`, `max` (lower / upper bound)
357
-
358
- * `:float` accepts a Ruby Float.
359
-
360
- - supported options: `min`, `max` (lower / upper bound)
361
-
362
- * `:number` accepts a Ruby Integer or Float.
363
-
364
- - supported options: `min`, `max` (lower / upper bound)
365
-
366
- * `:string` accepts a Ruby String.
367
-
368
- - supported options: `min`, `max` (bounds for string length)
369
-
370
- * `:symbol` accepts a Ruby Symbol.
371
-
372
- * `:object` accepts an arbitrary Ruby object (any object if no option is given).
373
-
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`.
384
-
385
- * `:array` accepts a Ruby Array.
386
-
387
- - supported options: `min`, `max` (bounds for array size) and `nil`: TODO
388
-
389
- - accepts a block with an arbitrary number of Type Lines.
390
-
391
- - TODO no lookahead for different arrays, see
392
- validator_array_test#test_multiple_arrays
393
-
394
- * `:hash` accepts a Ruby Hash or an `ActiveSupport::HashWithIndifferentAccess`.
395
-
396
- - accepts a block with an arbitrary number of Field Lines.
397
-
398
- - `allow_obsolete_keys`: If enabled (default `false`), this allows arbitrary
399
- keys within your hash. Keys not specified in your schema will not be
400
- validated further, but keys specified explicitly are still validated.
401
-
402
- * `:nil`: accepts a Ruby NilClass instance. If you want to allow `nil` as a
403
- value in a field, see above for the usage of the suffixes `-!` and `-?` for
404
- Field Lines.
405
-
406
- All types support the options `if` and `check` (see the section about Type Lines
407
- above).
408
-
409
- ## Short forms
410
-
411
- For convenience, the following short forms may be used (and combined if
412
- possible).
413
-
414
- ### Passing a type to a Field Line or schema
415
-
416
- Instead of adding a Type Line in the block of a Field Line, you can omit `do
417
- type ... end` and directly write the type after the key of the field.
418
-
419
- Note that when using this short form, you may not give a block to the Field
420
- Line.
421
-
422
- ```ruby
423
- # Long form
424
- req :name do
425
- type :string, min: 2, max: 5
426
- end
427
-
428
- # Short form
429
- req :name, :string, min: 2, max: 5
430
- ```
431
-
432
- This means that the value under the key `:name` of type Symbol must be a String
433
- containing 2 to 5 characters.
434
-
435
- The short form also works in the schema instantiation:
436
-
437
- ```ruby
438
- # Long form
439
- Schema.new do
440
- type :string, min: 2, max: 5
441
- end
442
-
443
- # Short form
444
- Schema.new(:string, min: 2, max: 5)
445
- ```
446
-
447
- This means that the data given to the schema must be a String that is between 2
448
- and 5 characters long.
449
-
450
- ### Passing multiple types at once
451
-
452
- You can specify several types at once by putting them in an array.
453
-
454
- Note that when using this short form, you may not give any options.
455
-
456
- ```ruby
457
- # Long form
458
- opt! :age do
459
- type :string
460
- type :integer
461
- type :boolean
462
- end
463
-
464
- # Short form
465
- opt! :age do
466
- type [:string, :integer, :boolean]
467
- end
468
- ```
469
-
470
- Combined with previous short form:
471
-
472
- ```ruby
473
- opt! :age, [:string, :integer, :boolean]
474
- ```
475
-
476
- This also works in the schema instantiation:
477
-
478
- ```ruby
479
- Schema.new([:string, :integer, :boolean])
480
- ```
481
-
482
- This means that the schema will validate any data of type String, Integer,
483
- TrueClass or FalseClass.
484
-
485
- ### Omitting the Type Line in a Field Line
486
-
487
- If you don't specify the type of a field, it will default to `:object` with no
488
- options, meaning that the field will accept any kind of data:
489
-
490
- ```ruby
491
- # Long form
492
- req? :child do
493
- type :object
494
- end
495
-
496
- # Short form
497
- req? :child
498
- ```
499
-
500
- ### Omitting the Type Line in schema instantiation
501
-
502
- If you don't give a Type Line to a schema, it will accept data of type Hash.
503
- Therefore, if you validate Hashes only, you can omit the Type Line and directly
504
- write Field Lines in the schema instantiation:
505
-
506
- ```ruby
507
- # Long form
508
- Schema.new do
509
- type :hash do
510
- req :name do
511
- # ...
512
- end
513
- end
514
- end
515
-
516
- # Short form
517
- Schema.new do
518
- req :name do
519
- # ...
520
- end
521
- end
522
- ```
523
-
524
- Note that this does not allow you to specify any options for the hash itself.
525
- You still need to specify `:hash` as a type if you want to pass any options to
526
- the hash (i.e. a `default`).
527
-
528
- ### Shortform for subtypes
529
-
530
- In case of nested arrays, you can group all Type Lines to a single one.
531
-
532
- Note that any options or block passed to the grouped Type Line will be given to
533
- the innermost (last) type.
534
-
535
- ```ruby
536
- # Long form
537
- type :array do
538
- type :integer, min: 3
539
- end
540
-
541
- # Short form
542
- type :array, :integer, min: 3
543
- ```
544
-
545
- A more complex example:
546
-
547
- Long form:
548
-
549
- ```ruby
550
- Schema.new do
551
- type :hash do
552
- req 'nutrition' do
553
- type :array do
554
- type :array do
555
- type :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
556
- opt! :food do
557
- type :object
558
- end
559
- opt! :drink do
560
- type :object
561
- end
562
- end
563
- end
564
- end
565
- end
566
- end
567
- end
568
- ```
569
-
570
- Short form (with this short form others from above):
571
-
572
- ```ruby
573
- Schema.new do
574
- req 'nutrition', :array, :array, :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
575
- opt! :food
576
- opt! :drink
577
- end
578
- end
579
- ```
580
-
581
- This example accepts a hash with exactly one String key 'nutrition' with value
582
- of type Array with children of type Array with children of type Hash in which at
583
- least one of the Symbol keys `:food` and `:drink` (with any non-nil value type)
584
- is present.
585
-
586
- ## Defaults
587
-
588
- Starting from version 2.4.0, Schemacop allows you to define default values at
589
- any point in your schema. If the validated data contains a nil value, it will be
590
- substituted by the given default value.
591
-
592
- Note that Schemacop never modifies the data you pass to it. If you want to
593
- benefit from Schemacop-applied defaults, you need to access the cloned, modified
594
- data returned by `validate` or `validate!`.
595
-
596
- Applying defaults is done before validating the substructure and before any type
597
- casting. The provided default will be validated same as user-supplied data, so
598
- if your given default does not validate properly, a validation error is thrown.
599
- Make sure your default values always match the underlying schema.
600
-
601
- Defaults can be specified at any point:
602
-
603
-
604
- ```ruby
605
- # Basic usage
606
- Schema.new do
607
- type :string, default: 'Hello World'
608
- end
609
-
610
- # The default given for the first type will match
611
- Schema.new do
612
- type :string, default: 'Hello World' # This will always be applied of no value is supplied
613
- type :integer, default: 42
614
- end
615
-
616
- # You can also pass entire hashes or arrays to your defaults
617
- Schema.new do
618
- req :foo, :hash, default: { foo: :bar } do
619
- req :foo, :symbol
620
- end
621
- req :bar, :array, :integer, default: [1, 2, 3]
622
- end
623
-
624
- # Defaults must match the given schema. The following will fail.
625
- Schema.new do
626
- req :foo, default: { bar: :baz } do
627
- req :foo
628
- end
629
- end
630
- ```
631
-
632
- ### Required data points
633
-
634
- Note that any *required* validation is done before applying the defaults. If you
635
- specify a `req` field, it must always be given, no matter if you have specified
636
- a default or not. Therefore, specifying `req` fields do not make sense in
637
- conjunction with defaults, as the default is always ignored.
638
-
639
- ## Type casting
640
-
641
- Starting from version 2.4.0, Schemacop allows you to specify type castings that
642
- can alter the validated data. Consider the following:
643
-
644
- ```ruby
645
- s = Schema.new do
646
- req :id, :integer, cast: [String]
647
- end
648
-
649
- data = s.validate!(id: '42')
650
- data # => { id: 42 }
651
- ```
652
-
653
- Note that Schemacop never modifies the data you pass to it. If you want to
654
- benefit from Schemacop-applied castings, you need to access the cloned, modified
655
- data returned by `validate` or `validate!`.
656
-
657
- ### Specifying type castings
658
-
659
- Type castings can be specified using two forms: Either as a hash or as an array.
660
- While using an array only allows you to specify the supported source types to be
661
- casted, using a hash allows you to specify custom casting logic as blocks.
662
-
663
- For hashes, the key must be a class and the value must be either `:default` for
664
- using a built-in caster or a callable object (proc or lambda) that receives the
665
- value and is supposed to cast it. If the value can't be casted, the proc must
666
- fail with an exception. The exception message will then be contained in the
667
- collected validation errors.
668
-
669
- Example:
670
-
671
- ```ruby
672
- Schema.new do
673
- # Pass array to `cast`. This enables casting from String or Float to Integer
674
- # using the built-in casters.
675
- req :id_1, :integer, cast: [String, Float]
676
-
677
- # Pass hash to `cast`. This enables casting from Float to Integer using the
678
- # built-in caster and from String to Integer using a custom callback.
679
- req :id_2, :integer, cast: { Float => :default, String => proc { |s| Integer(s) }
680
- end
681
- ```
682
-
683
- ### Built-in casters
684
-
685
- Schemacop comes with the following casters:
686
-
687
- - `String` to `Integer` and `Float`
688
- - `Float` to `Integer`
689
- - `Integer` to `Float`
690
-
691
- Note that all built-in casters are precise, so the string `foo` will fail with
692
- an error if casted to an Integer. When casting float values and strings
693
- containing float values to integers, the decimal places will be discarded
694
- however.
695
-
696
- ### Execution order
697
-
698
- The casting is done *before* the options `if` and `check` are evaluated.
699
- Example:
700
-
701
- ```ruby
702
- s = Schema.new do
703
- type :integer, if: proc { |i| i == 42 } # 1
704
- type :integer, check: proc { |i| i < 3 } # 2
705
- type :string
706
- end
707
-
708
- s.validate!('42') # 1 will match
709
- s.validate!('2') # 2 will match
710
- s.validate!('234') # 3 will match
711
- s.validate!(5) # Will fail, as nothing matches
712
- ```
713
-
714
- ### Caveats
715
-
716
- Casting only works with type definitions that only include one type. For
717
- instance, the `Numeric` validator includes both `Integer` and `Float`, which
718
- would made it unclear what to cast a string into:
719
-
720
- ```ruby
721
- # This does not work, as it is unclear whether to cast the String into an
722
- # Integer or a Float.
723
- type :number, cast: [String]
724
- ```
725
-
726
- The same also applies to booleans, as they compound both `TrueClass` and
727
- `FalseClass`. This may be tackled in future releases.
90
+ On the resulting data structure, you can use `to_json` to convert it into an
91
+ actual JSON string.
728
92
 
729
93
  ## Exceptions
730
94
 
731
95
  Schemacop will throw one of the following checked exceptions:
732
96
 
733
- * {Schemacop::Exceptions::InvalidSchemaError}
97
+ * `Schemacop::Exceptions::InvalidSchemaError`
734
98
 
735
99
  This exception is thrown when the given schema definition format is invalid.
736
100
 
737
- * {Schemacop::Exceptions::ValidationError}
101
+ * `Schemacop::Exceptions::ValidationError`
738
102
 
739
103
  This exception is thrown when the given data does not comply with the given
740
104
  schema definition.
741
105
 
742
- ## Known limitations
743
-
744
- * Schemacop does not yet allow cyclic structures with infinite depth.
745
-
746
- * Schemacop is not made for validating complex causalities (i.e. field `a`
747
- needs to be given only if field `b` is present).
748
-
749
- * Schemacop does not yet support string regex matching.
750
-
751
106
  ## Development
752
107
 
753
108
  To run tests: