schemacop 2.4.3 → 3.0.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) 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 +26 -0
  6. data/README.md +41 -692
  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 -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/{node_supporting_field.rb → v2/node_supporting_field.rb} +8 -10
  26. data/lib/schemacop/{node_supporting_type.rb → v2/node_supporting_type.rb} +12 -9
  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 -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 -289
  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 -818
  133. data/doc/file_list.html +0 -56
  134. data/doc/frames.html +0 -17
  135. data/doc/index.html +0 -818
  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 -31
  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 -90
  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 -71
  157. data/test/nil_dis_allow_test.rb +0 -41
  158. data/test/node_resolver_test.rb +0 -26
  159. data/test/short_forms_test.rb +0 -349
  160. data/test/test_helper.rb +0 -13
  161. data/test/types_test.rb +0 -84
  162. data/test/validator_array_test.rb +0 -97
  163. data/test/validator_boolean_test.rb +0 -15
  164. data/test/validator_float_test.rb +0 -57
  165. data/test/validator_hash_test.rb +0 -71
  166. data/test/validator_integer_test.rb +0 -46
  167. data/test/validator_nil_test.rb +0 -13
  168. data/test/validator_number_test.rb +0 -60
  169. data/test/validator_object_test.rb +0 -139
  170. data/test/validator_string_test.rb +0 -76
  171. data/test/validator_symbol_test.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5480656b86bcc3651dc95b413f84f410f245c07983918537867f0d21825f7380
4
- data.tar.gz: c971d9106db7fbbda30170300464b47aa2fdc2707722f1913062fca06693952a
3
+ metadata.gz: d5b9526bd5d67ae7a463ab9f5592fe0d23ae06090061b391f0e3f0e1cea5c6cd
4
+ data.tar.gz: d34ec2afccc0ddf874e4875babdbe3e733b5cd2b71cc85993920ca4d473e1e9c
5
5
  SHA512:
6
- metadata.gz: 6c5c235b8f16c232f29de106f9248fe40d6b50f6dbc7e779cedcac151435a9de9067d085a9e18b9b5bf37aaede611ba2f70cd83756ece7139747b6a9c0dc2aae
7
- data.tar.gz: 9e15fb4c0fb1734ffbd6d747a19e204079b4bc63de620f4d5dfd56ea1832a76228ef26f9288c72d1992f67613300922a1e81885291f87fd30958222d41eb2dca
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,32 @@
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
+
34
+ ## 2.4.4 (2020-03-9)
35
+
36
+ * Add option `allow_obsolete_keys` to `:hash` validator in order to allow
37
+ validating arbitrary hashes with dynamic keys not specifiable in schema.
38
+
13
39
  ## 2.4.3 (2020-03-05)
14
40
 
15
41
  * Only dup hashes and arrays but not the values when creating the modified
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,669 +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
262
- ```
263
-
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.
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
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
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
- * `:nil`: accepts a Ruby NilClass instance. If you want to allow `nil` as a
399
- value in a field, see above for the usage of the suffixes `-!` and `-?` for
400
- Field Lines.
401
-
402
- All types support the options `if` and `check` (see the section about Type Lines
403
- above).
404
-
405
- ## Short forms
406
-
407
- For convenience, the following short forms may be used (and combined if
408
- possible).
409
-
410
- ### Passing a type to a Field Line or schema
411
-
412
- Instead of adding a Type Line in the block of a Field Line, you can omit `do
413
- type ... end` and directly write the type after the key of the field.
414
-
415
- Note that when using this short form, you may not give a block to the Field
416
- Line.
417
-
418
- ```ruby
419
- # Long form
420
- req :name do
421
- type :string, min: 2, max: 5
422
- end
423
-
424
- # Short form
425
- req :name, :string, min: 2, max: 5
426
- ```
427
-
428
- This means that the value under the key `:name` of type Symbol must be a String
429
- containing 2 to 5 characters.
430
-
431
- The short form also works in the schema instantiation:
432
-
433
- ```ruby
434
- # Long form
435
- Schema.new do
436
- type :string, min: 2, max: 5
437
- end
438
-
439
- # Short form
440
- Schema.new(:string, min: 2, max: 5)
441
- ```
442
-
443
- This means that the data given to the schema must be a String that is between 2
444
- and 5 characters long.
445
-
446
- ### Passing multiple types at once
447
-
448
- You can specify several types at once by putting them in an array.
449
-
450
- Note that when using this short form, you may not give any options.
451
-
452
- ```ruby
453
- # Long form
454
- opt! :age do
455
- type :string
456
- type :integer
457
- type :boolean
458
- end
459
-
460
- # Short form
461
- opt! :age do
462
- type [:string, :integer, :boolean]
463
- end
464
- ```
465
-
466
- Combined with previous short form:
467
-
468
- ```ruby
469
- opt! :age, [:string, :integer, :boolean]
47
+ gem 'schemacop', '>= 3.0.0'
470
48
  ```
471
49
 
472
- This also works in the schema instantiation:
50
+ ## Schema specification
473
51
 
474
- ```ruby
475
- Schema.new([:string, :integer, :boolean])
476
- ```
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.
477
55
 
478
- This means that the schema will validate any data of type String, Integer,
479
- TrueClass or FalseClass.
56
+ * [Schema version 3](README_V3.md)
57
+ * [Schema version 2](README_V2.md) (legacy)
480
58
 
481
- ### Omitting the Type Line in a Field Line
59
+ ## JSON generation
482
60
 
483
- If you don't specify the type of a field, it will default to `:object` with no
484
- options, meaning that the field will accept any kind of data:
61
+ Using the method `as_json` on any V3 schema will produce a JSON schema compliant
62
+ to the JSON Schema standard.
485
63
 
486
64
  ```ruby
487
- # Long form
488
- req? :child do
489
- type :object
490
- end
65
+ Schemacop::Schema3.new :hash do
66
+ str! :name
67
+ end.as_json
491
68
 
492
- # Short form
493
- req? :child
69
+ # Will result in
70
+ {
71
+ type: :object,
72
+ properties: {
73
+ name: { type: :string }
74
+ },
75
+ additionalProperties: false,
76
+ required: [:name]
77
+ }
494
78
  ```
495
79
 
496
- ### Omitting the Type Line in schema instantiation
497
-
498
- If you don't give a Type Line to a schema, it will accept data of type Hash.
499
- Therefore, if you validate Hashes only, you can omit the Type Line and directly
500
- write Field Lines in the schema instantiation:
501
-
502
- ```ruby
503
- # Long form
504
- Schema.new do
505
- type :hash do
506
- req :name do
507
- # ...
508
- end
509
- end
510
- end
511
-
512
- # Short form
513
- Schema.new do
514
- req :name do
515
- # ...
516
- end
517
- end
518
- ```
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
-
524
- ### Shortform for subtypes
525
-
526
- In case of nested arrays, you can group all Type Lines to a single one.
527
-
528
- Note that any options or block passed to the grouped Type Line will be given to
529
- the innermost (last) type.
530
-
531
- ```ruby
532
- # Long form
533
- type :array do
534
- type :integer, min: 3
535
- end
536
-
537
- # Short form
538
- type :array, :integer, min: 3
539
- ```
540
-
541
- A more complex example:
542
-
543
- Long form:
544
-
545
- ```ruby
546
- Schema.new do
547
- type :hash do
548
- req 'nutrition' do
549
- type :array do
550
- type :array do
551
- type :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
552
- opt! :food do
553
- type :object
554
- end
555
- opt! :drink do
556
- type :object
557
- end
558
- end
559
- end
560
- end
561
- end
562
- end
563
- end
564
- ```
565
-
566
- Short form (with this short form others from above):
567
-
568
- ```ruby
569
- Schema.new do
570
- req 'nutrition', :array, :array, :hash, check: proc { |h| h.member?(:food) || h.member?(:drink) } do
571
- opt! :food
572
- opt! :drink
573
- end
574
- end
575
- ```
576
-
577
- This example accepts a hash with exactly one String key 'nutrition' with value
578
- of type Array with children of type Array with children of type Hash in which at
579
- least one of the Symbol keys `:food` and `:drink` (with any non-nil value type)
580
- is present.
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.
80
+ On the resulting data structure, you can use `to_json` to convert it into an
81
+ actual JSON string.
724
82
 
725
83
  ## Exceptions
726
84
 
@@ -735,15 +93,6 @@ Schemacop will throw one of the following checked exceptions:
735
93
  This exception is thrown when the given data does not comply with the given
736
94
  schema definition.
737
95
 
738
- ## Known limitations
739
-
740
- * Schemacop does not yet allow cyclic structures with infinite depth.
741
-
742
- * Schemacop is not made for validating complex causalities (i.e. field `a`
743
- needs to be given only if field `b` is present).
744
-
745
- * Schemacop does not yet support string regex matching.
746
-
747
96
  ## Development
748
97
 
749
98
  To run tests: