schemacop 2.4.7 → 3.0.0.rc0

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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +25 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +8 -0
  6. data/README.md +41 -708
  7. data/README_V2.md +775 -0
  8. data/README_V3.md +683 -0
  9. data/Rakefile +8 -12
  10. data/VERSION +1 -1
  11. data/lib/schemacop.rb +35 -37
  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} +4 -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/{node_supporting_field.rb → v2/node_supporting_field.rb} +8 -10
  26. data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +6 -3
  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 +217 -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 +775 -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 -145
  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 -833
  133. data/doc/file_list.html +0 -56
  134. data/doc/frames.html +0 -17
  135. data/doc/index.html +0 -833
  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/root_node.rb +0 -4
  145. data/lib/schemacop/validator/array_validator.rb +0 -30
  146. data/lib/schemacop/validator/float_validator.rb +0 -5
  147. data/lib/schemacop/validator/hash_validator.rb +0 -35
  148. data/lib/schemacop/validator/integer_validator.rb +0 -5
  149. data/lib/schemacop/validator/number_validator.rb +0 -19
  150. data/lib/schemacop/validator/object_validator.rb +0 -27
  151. data/lib/schemacop/validator/string_validator.rb +0 -37
  152. data/test/casting_test.rb +0 -118
  153. data/test/collector_test.rb +0 -45
  154. data/test/custom_check_test.rb +0 -93
  155. data/test/custom_if_test.rb +0 -95
  156. data/test/defaults_test.rb +0 -93
  157. data/test/empty_test.rb +0 -14
  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: b84cfd3771e9bf279d88c3c13d79432951bcd37f19db4f4ea9e275b45b4f70d5
4
- data.tar.gz: 7aef2cd86d1c4e42b0242137d721d5b9ab0f808083436fcee15fdf72e252336d
3
+ metadata.gz: d5b9526bd5d67ae7a463ab9f5592fe0d23ae06090061b391f0e3f0e1cea5c6cd
4
+ data.tar.gz: d34ec2afccc0ddf874e4875babdbe3e733b5cd2b71cc85993920ca4d473e1e9c
5
5
  SHA512:
6
- metadata.gz: 5bffa5cdc99b8d6e5aa478b182f87041aa9cf4616d62459fa386829a154c6fd2fde2c75d4c8504741e26eac6ab3478cad811fa33b363580bed7b5c1f04ff4ad6
7
- data.tar.gz: 7bb43ad27b2966851c30803a96b0b733b7ba9062784edd08fbb94aadc52c5c61e1142455bab0ad7d8a46c2c8e21572d26e3625f649ece2d18d468c2ea94b495b
6
+ metadata.gz: 8ebed3cb5eaf93f6f60f23231cfb3e88f49c00082641a3ba79531692c77802aa5e59eeb619a7f18c71cb23e8ea3438a5aef95c14769dd62dfd5f3b6d3fd50ec7
7
+ data.tar.gz: 184241911ffd97e75d4b9ecedc5d796b0b7269d63bff0b869e011011a382ab3f98ca75fbbdb25d507bab66dc9c0c56213df7212e49616210c15b94f1844f7cd7
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,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.0
3
+ - 2.6.2
4
+ - 2.7.1
4
5
  script:
5
6
  - bundle install
6
7
  - bundle exec rake test
@@ -10,6 +10,14 @@
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
+
13
21
  ## 2.4.7 (2020-07-02)
14
22
 
15
23
  * Return nil when casting an empty string to an Integer or a Float,
data/README.md CHANGED
@@ -3,49 +3,35 @@
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/).
11
10
 
12
- Examples:
11
+ ## Basic example
13
12
 
14
13
  ```ruby
15
- schema = Schema.new do
16
- req :naming, :hash do
17
- opt :first_name, :string
18
- req :last_name, :string
14
+ schema = Schemacop::Schema3.new :hash do
15
+ scm :group do
16
+ str! :name
19
17
  end
20
- opt! :age, :integer, min: 18
21
- req? :password do
22
- type :string, check: proc { |pw| pw.include?('*') }
23
- type :integer
18
+ str! :name
19
+ int? :age, minimum: 21
20
+ ary! :groups do
21
+ list :reference, path: :group
24
22
  end
25
23
  end
26
24
 
27
25
  schema.validate!(
28
- naming: { first_name: 'John',
29
- last_name: 'Doe' },
30
- age: 34,
31
- password: 'my*pass'
26
+ name: 'John Doe',
27
+ age: 42,
28
+ groups: [
29
+ { name: 'Group 1' },
30
+ { name: 'Group 2' }
31
+ ]
32
32
  )
33
33
  ```
34
34
 
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
35
  ## Installation
50
36
 
51
37
  To install the **Schemacop** gem:
@@ -58,685 +44,41 @@ To install it using `bundler` (recommended for any application), add it to your
58
44
  `Gemfile`:
59
45
 
60
46
  ```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
217
- ```
218
-
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`
227
-
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.
232
-
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.
236
-
237
- The following example illustrates the use of the option `check`: Consider a
238
- scenario in which you want the following rule set:
239
-
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:
245
-
246
- ```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
47
+ gem 'schemacop', '>= 3.0.0'
262
48
  ```
263
49
 
264
- This will include `Custom error` in the validation error message.
265
-
266
- ### Field Line
267
-
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.
50
+ ## Schema specification
275
51
 
276
- - The suffix `-?` means that the field may be nil.
52
+ The actual schema definition depends on the schema version you're using.
53
+ Schemacop 3 supports version 3 and also the legacy version 2 for backwards
54
+ compatibility. For version 1, you need to use the `1.x` versions of schemacop.
277
55
 
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).
56
+ * [Schema version 3](README_V3.md)
57
+ * [Schema version 2](README_V2.md) (legacy)
281
58
 
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).
59
+ ## JSON generation
284
60
 
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`.
61
+ Using the method `as_json` on any V3 schema will produce a JSON schema compliant
62
+ to the JSON Schema standard.
297
63
 
298
64
  ```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
310
- ```
311
-
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
65
+ Schemacop::Schema3.new :hash do
66
+ str! :name
67
+ end.as_json
316
68
 
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
69
+ # Will result in
70
+ {
71
+ type: :object,
72
+ properties: {
73
+ name: { type: :string }
74
+ },
75
+ additionalProperties: false,
76
+ required: [:name]
77
+ }
343
78
  ```
344
79
 
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
- # You can also specify blocks (without params) as defaults that will be
632
- # evaluated at time of validation.
633
- Schema.new do
634
- opt :year, :integer, default: ->() { Time.now.year }
635
- end
636
-
637
- # If, for some very specific reason, you *need* the default to be an actual
638
- # proc, wrap it inside another proc.
639
- Schema.new do
640
- opt :myproc, Proc, default: ->() { ->() { 42 } }
641
- end
642
- ```
643
-
644
- ### Required data points
645
-
646
- Note that any *required* validation is done before applying the defaults. If you
647
- specify a `req` field, it must always be given, no matter if you have specified
648
- a default or not. Therefore, specifying `req` fields do not make sense in
649
- conjunction with defaults, as the default is always ignored.
650
-
651
- ## Type casting
652
-
653
- Starting from version 2.4.0, Schemacop allows you to specify type castings that
654
- can alter the validated data. Consider the following:
655
-
656
- ```ruby
657
- s = Schema.new do
658
- req :id, :integer, cast: [String]
659
- end
660
-
661
- data = s.validate!(id: '42')
662
- data # => { id: 42 }
663
- ```
664
-
665
- Note that Schemacop never modifies the data you pass to it. If you want to
666
- benefit from Schemacop-applied castings, you need to access the cloned, modified
667
- data returned by `validate` or `validate!`.
668
-
669
- ### Specifying type castings
670
-
671
- Type castings can be specified using two forms: Either as a hash or as an array.
672
- While using an array only allows you to specify the supported source types to be
673
- casted, using a hash allows you to specify custom casting logic as blocks.
674
-
675
- For hashes, the key must be a class and the value must be either `:default` for
676
- using a built-in caster or a callable object (proc or lambda) that receives the
677
- value and is supposed to cast it. If the value can't be casted, the proc must
678
- fail with an exception. The exception message will then be contained in the
679
- collected validation errors.
680
-
681
- Example:
682
-
683
- ```ruby
684
- Schema.new do
685
- # Pass array to `cast`. This enables casting from String or Float to Integer
686
- # using the built-in casters.
687
- req :id_1, :integer, cast: [String, Float]
688
-
689
- # Pass hash to `cast`. This enables casting from Float to Integer using the
690
- # built-in caster and from String to Integer using a custom callback.
691
- req :id_2, :integer, cast: { Float => :default, String => proc { |s| Integer(s) }
692
- end
693
- ```
694
-
695
- ### Built-in casters
696
-
697
- Schemacop comes with the following casters:
698
-
699
- - `String` to `Integer` and `Float`
700
- - `Float` to `Integer`
701
- - `Integer` to `Float`
702
-
703
- Note that all built-in casters are precise, so the string `foo` will fail with
704
- an error if casted to an Integer. When casting float values and strings
705
- containing float values to integers, the decimal places will be discarded
706
- however.
707
-
708
- ### Execution order
709
-
710
- The casting is done *before* the options `if` and `check` are evaluated.
711
- Example:
712
-
713
- ```ruby
714
- s = Schema.new do
715
- type :integer, if: proc { |i| i == 42 } # 1
716
- type :integer, check: proc { |i| i < 3 } # 2
717
- type :string
718
- end
719
-
720
- s.validate!('42') # 1 will match
721
- s.validate!('2') # 2 will match
722
- s.validate!('234') # 3 will match
723
- s.validate!(5) # Will fail, as nothing matches
724
- ```
725
-
726
- ### Caveats
727
-
728
- Casting only works with type definitions that only include one type. For
729
- instance, the `Numeric` validator includes both `Integer` and `Float`, which
730
- would made it unclear what to cast a string into:
731
-
732
- ```ruby
733
- # This does not work, as it is unclear whether to cast the String into an
734
- # Integer or a Float.
735
- type :number, cast: [String]
736
- ```
737
-
738
- The same also applies to booleans, as they compound both `TrueClass` and
739
- `FalseClass`. This may be tackled in future releases.
80
+ On the resulting data structure, you can use `to_json` to convert it into an
81
+ actual JSON string.
740
82
 
741
83
  ## Exceptions
742
84
 
@@ -751,15 +93,6 @@ Schemacop will throw one of the following checked exceptions:
751
93
  This exception is thrown when the given data does not comply with the given
752
94
  schema definition.
753
95
 
754
- ## Known limitations
755
-
756
- * Schemacop does not yet allow cyclic structures with infinite depth.
757
-
758
- * Schemacop is not made for validating complex causalities (i.e. field `a`
759
- needs to be given only if field `b` is present).
760
-
761
- * Schemacop does not yet support string regex matching.
762
-
763
96
  ## Development
764
97
 
765
98
  To run tests: