json-spec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +960 -0
- data/Rakefile +10 -0
- data/api_documentation.md +611 -0
- data/cachivache/.gitignore +1 -0
- data/cachivache/Gemfile +4 -0
- data/cachivache/README.md +247 -0
- data/cachivache/Rakefile +19 -0
- data/cachivache/Vagrantfile +70 -0
- data/cachivache/cachivache.rb +59 -0
- data/cachivache/lib/let-behaviour.rb +27 -0
- data/cachivache/lib/rake-helper.rb +131 -0
- data/cachivache/lib/sh-file-context.rb +39 -0
- data/cachivache/lib/sh-if-context.rb +31 -0
- data/cachivache/stuff/.gitkeep +0 -0
- data/cachivache/stuff/ruby-json-spec.rb +22 -0
- data/examples/example-1-simple.rb +66 -0
- data/examples/example-2-default-expectations.rb +63 -0
- data/examples/example-3-each-field.rb +104 -0
- data/examples/example-4-to-be-as-defined-in.rb +117 -0
- data/examples/example-5-custom-expectations.rb +153 -0
- data/examples/example-6-custom-messages.rb +36 -0
- data/examples/example-7-full-example.rb +231 -0
- data/examples/fixtures.rb +77 -0
- data/examples/validation-printer.rb +47 -0
- data/json-spec.gemspec +29 -0
- data/lib/cabeza-de-termo/json-spec/errors/error.rb +6 -0
- data/lib/cabeza-de-termo/json-spec/errors/expectation-not-found-error.rb +8 -0
- data/lib/cabeza-de-termo/json-spec/errors/modifier-not-found-error.rb +8 -0
- data/lib/cabeza-de-termo/json-spec/errors/unkown-json-type-error.rb +8 -0
- data/lib/cabeza-de-termo/json-spec/errors/validation-error.rb +8 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/default-expectations/default-expectation-builder.rb +29 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/default-expectations/default-expectations-mapping.rb +54 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/block-expectation-definition.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/class-expectation-definition.rb +15 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/expectation-definition.rb +19 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/expectations-definition-builder.rb +136 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/expecting-all-of-expectation-definition.rb +23 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/expecting-any-of-expectation-definition.rb +23 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/expecting-expectation-definition.rb +17 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-builders/negating-expectation-definition.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/expectation-library-definition-builder.rb +35 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/modifier-builders/class-modifier-definition.rb +15 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/modifier-builders/composing-modifiers-definition.rb +28 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/modifier-builders/modifier-definition.rb +13 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/definition-builders/modifier-builders/modifiers-definition-builder.rb +73 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/expectations-library.rb +150 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/initializers/default-library-initializer.rb +265 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/initializers/library-initializer.rb +9 -0
- data/lib/cabeza-de-termo/json-spec/expectations-library/messages/expectation-messages-mapping.rb +27 -0
- data/lib/cabeza-de-termo/json-spec/expectations/abstract-expectation.rb +39 -0
- data/lib/cabeza-de-termo/json-spec/expectations/all-expectations-composite.rb +33 -0
- data/lib/cabeza-de-termo/json-spec/expectations/any-expectation-composite.rb +33 -0
- data/lib/cabeza-de-termo/json-spec/expectations/block-expectation.rb +28 -0
- data/lib/cabeza-de-termo/json-spec/expectations/expectation.rb +51 -0
- data/lib/cabeza-de-termo/json-spec/expectations/is-email-expectation.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/expectations/is-scalar-expectation.rb +17 -0
- data/lib/cabeza-de-termo/json-spec/expectations/is-url-expectation.rb +21 -0
- data/lib/cabeza-de-termo/json-spec/expectations/negated-expectation.rb +31 -0
- data/lib/cabeza-de-termo/json-spec/expectations/runner/abstract-expectations-runner.rb +27 -0
- data/lib/cabeza-de-termo/json-spec/expectations/runner/can-be-absent-expectations-runner.rb +50 -0
- data/lib/cabeza-de-termo/json-spec/expectations/runner/can-be-null-expectations-runner.rb +48 -0
- data/lib/cabeza-de-termo/json-spec/expectations/runner/expectations-runner.rb +43 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-any-of.rb +62 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-anything.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-each-field.rb +58 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-each.rb +58 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-expression.rb +314 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-field-name.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-field.rb +76 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-list.rb +40 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-object.rb +82 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-scalar.rb +20 -0
- data/lib/cabeza-de-termo/json-spec/expressions/json-spec.rb +174 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/abstract-instantiator.rb +9 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/all-expectations-composite-instantiator.rb +12 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/any-expectation-composite-instantiator.rb +12 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/block-expectation-instantiator.rb +16 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/composite-instantiator.rb +45 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/modifier-composite-instantiator.rb +12 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/negated-expectation-instantiator.rb +20 -0
- data/lib/cabeza-de-termo/json-spec/instantiators/patial-application-instantiator.rb +26 -0
- data/lib/cabeza-de-termo/json-spec/json-spec.rb +2 -0
- data/lib/cabeza-de-termo/json-spec/message-formatters/block-message-formatter.rb +15 -0
- data/lib/cabeza-de-termo/json-spec/message-formatters/erb-message-formatter.rb +60 -0
- data/lib/cabeza-de-termo/json-spec/message-formatters/message-formatter.rb +9 -0
- data/lib/cabeza-de-termo/json-spec/metaprogramming/message-send.rb +37 -0
- data/lib/cabeza-de-termo/json-spec/metaprogramming/message.rb +37 -0
- data/lib/cabeza-de-termo/json-spec/metaprogramming/object-method.rb +14 -0
- data/lib/cabeza-de-termo/json-spec/modifiers/can-be-absent-modifier.rb +13 -0
- data/lib/cabeza-de-termo/json-spec/modifiers/can-be-null-modifier.rb +13 -0
- data/lib/cabeza-de-termo/json-spec/modifiers/expression-modifier.rb +9 -0
- data/lib/cabeza-de-termo/json-spec/modifiers/modifier-composite.rb +27 -0
- data/lib/cabeza-de-termo/json-spec/signals/signal.rb +6 -0
- data/lib/cabeza-de-termo/json-spec/signals/skip-branch-signal.rb +8 -0
- data/lib/cabeza-de-termo/json-spec/utilities/bind.rb +20 -0
- data/lib/cabeza-de-termo/json-spec/utilities/range.rb +70 -0
- data/lib/cabeza-de-termo/json-spec/value-holders/accessors-chain.rb +27 -0
- data/lib/cabeza-de-termo/json-spec/value-holders/missing-value.rb +21 -0
- data/lib/cabeza-de-termo/json-spec/value-holders/value-holder.rb +135 -0
- data/lib/cabeza-de-termo/json-spec/version.rb +5 -0
- data/lib/cabeza-de-termo/json-spec/walkers/expression-walker.rb +66 -0
- data/lib/cabeza-de-termo/json-spec/walkers/json-expectations-runner.rb +214 -0
- data/lib/cabeza-de-termo/json-spec/walkers/json-expression-explainer.rb +183 -0
- data/lib/cabeza-de-termo/json-spec/walkers/reporter/expectation-report.rb +63 -0
- data/lib/cabeza-de-termo/json-spec/walkers/reporter/json-expectations-reporter.rb +111 -0
- data/lib/cabeza-de-termo/json-spec/walkers/validator/json-validator-error.rb +29 -0
- data/lib/cabeza-de-termo/json-spec/walkers/validator/json-validator.rb +133 -0
- data/lib/cabeza-de-termo/json-spec/walkers/value-holders-stack-behaviour.rb +57 -0
- metadata +242 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd9a98802b2e9d9d82dff4d9fd8b6b174812f195
|
4
|
+
data.tar.gz: 071fdf0b74f43cae45e079b089a14fd0fc2af5fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 797adfabb1a8111259da2fdc097d47f68914414a07a8fdc760cd5aaad744e91bdece18dbf735a2e78124361c57779b3d3b127ba5deeaeb51fae5c0e8038248d2
|
7
|
+
data.tar.gz: 59e894989cc21acb4abdc0feb9bcee8cfd4ef892111e6d39195ed0891ae543fbb564b499e57c73cbc1e995c8a4d60e95f31b10d5d1b153e2c5e47650eb6307e9
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Martin Rubi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,960 @@
|
|
1
|
+
# CabezaDeTermo::JsonSpec
|
2
|
+
|
3
|
+
A framework to declare expectations and verify that a json object satisfies those expectations. You can use this expectations to validate jsons you send or receive in your application, or to test your API with unit tests.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'json-spec'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install json-spec
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
If you want to read the supported expressions and expectations by `json-spec`, see the [API documentation](api_documentation.md).
|
24
|
+
|
25
|
+
If you want to jump to a running example, see the [full example](examples/example-7-full-example.rb).
|
26
|
+
|
27
|
+
Otherwise walk through this tutorial where we will be writting a spec to validate a partial specification of the `composer.json` used by a [dyslexic cousin](https://github.com/cabeza-de-termo/php-json-spec) of this project:
|
28
|
+
|
29
|
+
```json
|
30
|
+
{
|
31
|
+
"name": "cabeza-de-termo/json-spec",
|
32
|
+
"type": "library",
|
33
|
+
"description": "A framework to declare expectations and verify that a json object complies with those expectations. You can use this json expectations to validate jsons you send or receive in your application, or to test your API with unit tests.",
|
34
|
+
"keywords": ["json", "assertions", "expectations", "validation", "phpunit"],
|
35
|
+
"homepage": "https://github.com/cabeza-de-termo/php-json-spec",
|
36
|
+
"license": "MIT",
|
37
|
+
"authors": [
|
38
|
+
{
|
39
|
+
"name": "Martin Rubi",
|
40
|
+
"email": "martin.rubi@martinrubi.com"
|
41
|
+
}
|
42
|
+
],
|
43
|
+
"require": {
|
44
|
+
"php": ">=5.4.0"
|
45
|
+
},
|
46
|
+
"require-dev": {
|
47
|
+
"phpunit/phpunit": "^4",
|
48
|
+
"phpdocumentor/phpdocumentor": "2.*"
|
49
|
+
},
|
50
|
+
"autoload": {
|
51
|
+
"psr-4": {
|
52
|
+
"CabezaDeTermo\\JsonSpec\\": ["src/"],
|
53
|
+
"CabezaDeTermo\\JsonSpec\\Tests\\": ["tests/"]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
## Starting with a simple validation
|
60
|
+
|
61
|
+
We will declare each expression we expect the `composer.json` to have, and for each expression we will declare expectations on it:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'cabeza-de-termo/json-spec/json-spec'
|
65
|
+
|
66
|
+
valid_licenses =
|
67
|
+
['Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause',
|
68
|
+
'BSD-4-Clause' ,'GPL-2.0', 'GPL-2.0+', 'GPL-3.0',
|
69
|
+
'GPL-3.0+', 'LGPL-2.1', 'LGPL-2.1+', 'LGPL-3.0',
|
70
|
+
'LGPL-3.0+', 'MIT']
|
71
|
+
|
72
|
+
json_spec = CabezaDeTermo::JsonSpec::JsonSpec.new do
|
73
|
+
expect_an(:object) do
|
74
|
+
expect('name') .to_be_defined .not_blank
|
75
|
+
expect('type') .to_be_defined .not_blank
|
76
|
+
expect('description') .to_be_defined .not_blank
|
77
|
+
expect('keywords') .to_be_a(:list) .can_be_absent .not_empty do
|
78
|
+
each do
|
79
|
+
expect_a(:scalar) .not_blank
|
80
|
+
end
|
81
|
+
end
|
82
|
+
expect('homepage') .to_be_url
|
83
|
+
expect('license') .to_be_defined .to_be_in(valid_licenses)
|
84
|
+
expect('authors') .to_be_a(:list) .to_be_defined .not_empty do
|
85
|
+
each do
|
86
|
+
expect_an(:object) do
|
87
|
+
expect('name') .to_be_defined .not_blank
|
88
|
+
expect('email') .to_be_defined .to_be_email
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
expect('require') .to_be_an(:object) .can_be_absent
|
93
|
+
expect('require-dev') .to_be_an(:object) .can_be_absent
|
94
|
+
expect('autoload') .to_be_an(:object) .to_be_defined do
|
95
|
+
expect('psr-0') .to_be_an(:object) .can_be_absent
|
96
|
+
expect('psr-4') .to_be_an(:object) .can_be_absent
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
That's it. Now we can validate a json string by running:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
validator = json_spec.validate_string json_string
|
106
|
+
|
107
|
+
puts validator.errors
|
108
|
+
puts validator.unexpected_fields
|
109
|
+
```
|
110
|
+
|
111
|
+
If you want to run this example, see the [first example](examples/example-1-simple.rb) in the `examples/` folder.
|
112
|
+
|
113
|
+
## Defining default expectations for each expression
|
114
|
+
|
115
|
+
One thing you may have noticed about the previous example is that it includes a lot of repeated `.to_be_defined` expectations on many fields. It would be easier if we could just state that `.to_be_defined` is expected for every field.
|
116
|
+
|
117
|
+
We can do that in two different ways.
|
118
|
+
|
119
|
+
If we want to declare default expectations at a global scope, i.e., for every expression used in any place, then we can do:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
123
|
+
default_expectations do
|
124
|
+
for_every_field do
|
125
|
+
to_be_defined
|
126
|
+
to_be_string
|
127
|
+
not_blank
|
128
|
+
...
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
If we want to declare default expectations for a json_spec only then we can do:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
json_spec.define do
|
138
|
+
default_expectations do
|
139
|
+
for_every_field do
|
140
|
+
to_be_defined
|
141
|
+
to_be_string
|
142
|
+
not_blank
|
143
|
+
...
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
We can declare default expectations at any scope for different expressions:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
default_expectations do
|
153
|
+
for_every_object do
|
154
|
+
...
|
155
|
+
end
|
156
|
+
|
157
|
+
for_every_list do
|
158
|
+
...
|
159
|
+
end
|
160
|
+
|
161
|
+
for_every_scalar do
|
162
|
+
...
|
163
|
+
end
|
164
|
+
|
165
|
+
for_every_field do
|
166
|
+
...
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
If we want to get rid of the current default expressions, in the corresponding scope we declare any of:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
default_expectations do
|
175
|
+
drop_all_expectations
|
176
|
+
drop_expectations_for(:objects)
|
177
|
+
drop_expectations_for(:lists)
|
178
|
+
drop_expectations_for(:fields)
|
179
|
+
drop_expectations_for(:scalars)
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
You can see which default expectations are used by the framework in the [DefaultLibraryInitializer](lib/cabeza-de-termo/json-spec/expectations-library/initializers/default-library-initializer.rb) class.
|
184
|
+
|
185
|
+
So, back to our example, now the validation looks like:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
189
|
+
default_expectations do
|
190
|
+
for_every_field do
|
191
|
+
to_be_defined
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
valid_licenses =
|
197
|
+
['Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause',
|
198
|
+
'BSD-4-Clause' ,'GPL-2.0', 'GPL-2.0+', 'GPL-3.0',
|
199
|
+
'GPL-3.0+', 'LGPL-2.1', 'LGPL-2.1+', 'LGPL-3.0',
|
200
|
+
'LGPL-3.0+', 'MIT']
|
201
|
+
|
202
|
+
json_spec = CabezaDeTermo::JsonSpec::JsonSpec.new do
|
203
|
+
expect_an(:object) do
|
204
|
+
expect('name') .not_blank
|
205
|
+
expect('type') .not_blank
|
206
|
+
expect('description') .not_blank
|
207
|
+
expect('keywords') .to_be_a(:list) .can_be_absent .not_empty do
|
208
|
+
each do
|
209
|
+
expect(:scalar) .not_blank
|
210
|
+
end
|
211
|
+
end
|
212
|
+
expect('homepage') .to_be_url
|
213
|
+
expect('license') .to_be_in(valid_licenses)
|
214
|
+
expect('authors') .to_be_a(:list) .not_empty do
|
215
|
+
each do
|
216
|
+
expect_an(:object) do
|
217
|
+
expect('name') .not_blank
|
218
|
+
expect('email') .to_be_email
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
expect('require') .to_be_an(:object) .can_be_absent
|
223
|
+
expect('require-dev') .to_be_an(:object) .can_be_absent
|
224
|
+
expect('autoload') .to_be_an(:object) do
|
225
|
+
expect('psr-0') .to_be_an(:object) .can_be_absent
|
226
|
+
expect('psr-4') .to_be_an(:object) .can_be_absent
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
If you want to run this example, see the [second example](examples/example-2-default-expectations.rb) in the `examples/` folder.
|
233
|
+
|
234
|
+
## Declaring expectations for fields not known in advance.
|
235
|
+
|
236
|
+
If you run the previous example, you may notice that it outputs the folowing:
|
237
|
+
|
238
|
+
```
|
239
|
+
- Failed expectations: 0
|
240
|
+
|
241
|
+
- Unexpected fields: 5
|
242
|
+
Field: '@.require' message: "An unexpected 'php' field was found."
|
243
|
+
Field: '@.require-dev' message: "An unexpected 'phpunit/phpunit' field was found."
|
244
|
+
Field: '@.require-dev' message: "An unexpected 'phpdocumentor/phpdocumentor' field was found."
|
245
|
+
Field: '@.autoload.psr-4' message: "An unexpected 'CabezaDeTermoJsonSpec\' field was found."
|
246
|
+
Field: '@.autoload.psr-4' message: "An unexpected 'CabezaDeTermo\JsonSpec\Tests\' field was found."
|
247
|
+
```
|
248
|
+
|
249
|
+
That is because the `composer.json` has the following sections:
|
250
|
+
|
251
|
+
```json
|
252
|
+
"require": {
|
253
|
+
"php": ">=5.4.0"
|
254
|
+
},
|
255
|
+
"require-dev": {
|
256
|
+
"phpunit/phpunit": "^4",
|
257
|
+
"phpdocumentor/phpdocumentor": "2.*"
|
258
|
+
}
|
259
|
+
|
260
|
+
...
|
261
|
+
|
262
|
+
"psr-4": {
|
263
|
+
"CabezaDeTermoJsonSpec\\\\": ["src/"],
|
264
|
+
"CabezaDeTermo\\\\JsonSpec\\\\Tests\\\\": ["tests/"]
|
265
|
+
}
|
266
|
+
```
|
267
|
+
|
268
|
+
with fields with names that we don't know in advance. They can be anything, as long as they comply with what `composer.json` expects. So we also want to declare expectations on unkown fields.
|
269
|
+
|
270
|
+
To do that, we use the `:each_field` expectation:
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
expect('require') .to_be_an(:object) .can_be_absent do
|
274
|
+
each_field do
|
275
|
+
expect_name .not_blank
|
276
|
+
expect_a(:scalar) .not_blank
|
277
|
+
end
|
278
|
+
end
|
279
|
+
...
|
280
|
+
expect('psr-0') .to_be_an(:object) .can_be_absent do
|
281
|
+
each_field do
|
282
|
+
expect_name
|
283
|
+
|
284
|
+
expect_a(:list) .not_empty do
|
285
|
+
each do
|
286
|
+
expect_a(:scalar) .not_blank
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
```
|
292
|
+
|
293
|
+
If you want to run this example, see the [third example](examples/example-3-each-field.rb) in the `examples/` folder.
|
294
|
+
|
295
|
+
|
296
|
+
## Refactoring the expectations
|
297
|
+
|
298
|
+
So by now we have the following expectations:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
json_spec = CabezaDeTermo::JsonSpec::JsonSpec.new do
|
302
|
+
expect_an(:object) do
|
303
|
+
expect('name') .not_blank
|
304
|
+
expect('type') .not_blank
|
305
|
+
expect('description') .not_blank
|
306
|
+
|
307
|
+
expect('keywords') .to_be_a(:list) .can_be_absent .not_empty do
|
308
|
+
each do
|
309
|
+
expect_a(:scalar) .not_blank
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
expect('homepage') .to_be_url
|
314
|
+
expect('license') .to_be_in(valid_licenses)
|
315
|
+
|
316
|
+
expect('authors') .to_be_a(:list) .not_empty do
|
317
|
+
each do
|
318
|
+
expect_an(:object) do
|
319
|
+
expect('name') .not_blank
|
320
|
+
expect('email') .to_be_email
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
expect('require') .to_be_an(:object) .can_be_absent do
|
326
|
+
each_field do
|
327
|
+
expect_name .not_blank
|
328
|
+
expect_a(:scalar) .not_blank
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
expect('require-dev') .to_be_an(:object) .can_be_absent do
|
333
|
+
each_field do
|
334
|
+
expect_name.not_blank
|
335
|
+
expect_a(:scalar) .not_blank
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
expect('autoload') .to_be_an(:object) do
|
340
|
+
expect('psr-0') .to_be_an(:object) .can_be_absent do
|
341
|
+
each_field do
|
342
|
+
expect_name
|
343
|
+
|
344
|
+
expect_a(:list) .not_empty do
|
345
|
+
each do
|
346
|
+
expect_a(:scalar) .not_blank
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
expect('psr-4') .to_be_an(:object) .can_be_absent do
|
353
|
+
each_field do
|
354
|
+
expect_name
|
355
|
+
|
356
|
+
expect_a(:list) .not_empty do
|
357
|
+
each do
|
358
|
+
expect_a(:scalar) .not_blank
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
expect('classmap') .to_be_a(:list) .can_be_absent
|
365
|
+
|
366
|
+
expect('files') .to_be_a(:list) .can_be_absent
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
This have several problems. One is its extension. The spec just got too long. Second, now it is a structure of expectations on json expressions that lacks of some intention revealing names about the expressions. For instance, this section
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
expect_an(:object) do
|
376
|
+
expect('name') .not_blank
|
377
|
+
expect('email') .to_be_email
|
378
|
+
end
|
379
|
+
```
|
380
|
+
|
381
|
+
refers to an author, but if we only look at it without its parent expression, we don't know that.
|
382
|
+
And third, and worst than the previous reasons, is that we are duplicating expectations for `require` and `require-dev`.
|
383
|
+
|
384
|
+
So it would be nice to be able to organize the expectations somehow.
|
385
|
+
|
386
|
+
We can put expectations in methods, and then call those methods from within the json_spec. This methods can be in any class, but we are going to create a `ComposerJson` class to keep the `composer.json` expectations in one place.
|
387
|
+
So now we have the class
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
class ComposerJson
|
391
|
+
def spec()
|
392
|
+
CabezaDeTermo::JsonSpec::JsonSpec.new do |json_spec|
|
393
|
+
json_spec.expect_an(:object) do |object|
|
394
|
+
object.expect('name') .not_blank
|
395
|
+
object.expect('type') .not_blank
|
396
|
+
object.expect('description') .not_blank
|
397
|
+
object.expect('keywords') .to_be_as_defined_in(self, :keywords_spec)
|
398
|
+
object.expect('homepage').to_be_url
|
399
|
+
object.expect('license') .to_be_in(self.valid_licenses)
|
400
|
+
object.expect('authors') .to_be_as_defined_in(self, :authors_spec)
|
401
|
+
object.expect('require') .to_be_as_defined_in(self, :require_spec)
|
402
|
+
object.expect('require-dev') .to_be_as_defined_in(self, :require_spec)
|
403
|
+
object.expect('autoload') .to_be_as_defined_in(self, :autoload_spec)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def keywords_spec(json_spec)
|
409
|
+
json_spec .to_be_a(:list) .can_be_absent .not_empty do
|
410
|
+
each do
|
411
|
+
expect_a(:scalar) .not_blank
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def authors_spec(json_spec)
|
417
|
+
json_spec .to_be_a(:list) .not_empty do |list|
|
418
|
+
list.each do |each|
|
419
|
+
each.to_be_as_defined_in(self, :author_spec)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def author_spec(json_spec)
|
425
|
+
json_spec .expect_an(:object) do
|
426
|
+
expect('name') .not_blank
|
427
|
+
expect('email') .to_be_email
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def require_spec(json)
|
432
|
+
json .to_be_an(:object) .can_be_absent do
|
433
|
+
each_field do
|
434
|
+
expect_name .not_blank
|
435
|
+
expect_a(:scalar) .not_blank
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def autoload_spec(json)
|
441
|
+
json .to_be_an(:object) do |object|
|
442
|
+
object.expect('psr-0') .to_be_as_defined_in(self, :psr_spec)
|
443
|
+
object.expect('psr-4') .to_be_as_defined_in(self, :psr_spec)
|
444
|
+
object.expect('classmap') .to_be_as_defined_in(self, :classmaps_spec)
|
445
|
+
object.expect('files') .to_be_as_defined_in(self, :files_spec)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def psr_spec(json_spec)
|
450
|
+
json_spec .to_be_an(:object) .can_be_absent do
|
451
|
+
each_field do
|
452
|
+
expect_name
|
453
|
+
|
454
|
+
expect_a(:list) .not_empty do
|
455
|
+
each do
|
456
|
+
expect_a(:scalar) .not_blank
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def classmaps_spec(json_spec)
|
464
|
+
json_spec .to_be_a(:list) .can_be_absent .not_empty
|
465
|
+
end
|
466
|
+
|
467
|
+
def files_spec(json_spec)
|
468
|
+
json_spec .to_be_a(:list) .can_be_absent .not_empty
|
469
|
+
end
|
470
|
+
|
471
|
+
def valid_licenses()
|
472
|
+
['Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause',
|
473
|
+
'BSD-4-Clause' ,'GPL-2.0', 'GPL-2.0+', 'GPL-3.0',
|
474
|
+
'GPL-3.0+', 'LGPL-2.1', 'LGPL-2.1+', 'LGPL-3.0',
|
475
|
+
'LGPL-3.0+', 'MIT']
|
476
|
+
end
|
477
|
+
end
|
478
|
+
```
|
479
|
+
|
480
|
+
and to run the validation all we have to do is
|
481
|
+
|
482
|
+
```ruby
|
483
|
+
validator = ComposerJson.new.spec.validate_string(json_string)
|
484
|
+
```
|
485
|
+
|
486
|
+
If you want to validate a not very complex json, you can get away with it without factorizing the expectations. But as soon as the validation gets more complex, you can refactor the expectations using `:to_be_as_defined_in(some_object, :some_method)`.
|
487
|
+
|
488
|
+
One thing to notice in this example is that we declared things like
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
CabezaDeTermo::JsonSpec::JsonSpec.new do |json_spec|
|
492
|
+
json_spec.expect_an(:object) do |object|
|
493
|
+
...
|
494
|
+
end
|
495
|
+
end
|
496
|
+
```
|
497
|
+
|
498
|
+
instead of
|
499
|
+
|
500
|
+
```ruby
|
501
|
+
CabezaDeTermo::JsonSpec::JsonSpec.new do
|
502
|
+
json_spec.expect_an(:object) do
|
503
|
+
...
|
504
|
+
end
|
505
|
+
end
|
506
|
+
```
|
507
|
+
|
508
|
+
That is because when we don't pass a parameter to the defintion block, it changes the binding of self, and the we can not declare thinkgs like
|
509
|
+
|
510
|
+
```ruby
|
511
|
+
.to_be_as_defined_in(self, :keywords_spec)
|
512
|
+
```
|
513
|
+
|
514
|
+
Changing the binding to self is usually a bad idea, but in the definitions of this framework it will only do that if no parameter is given to the definition block.
|
515
|
+
|
516
|
+
If you want to run this example, see the [fourth example](examples/example-4-to-be-as-defined-in.rb) in the `examples/` folder.
|
517
|
+
|
518
|
+
## Expecting different structures for the same expression
|
519
|
+
|
520
|
+
If we look at the `composer.json` definition, we will notice that sometimes it accepts different structures for the same field. For instance, for `psr-4` values, it can take a list of folder strings or a single folder string.
|
521
|
+
|
522
|
+
To expect different structures on the same object, we use `:expect(:any_of)`:
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
expect('psr-4') .to_be_a(:list) do
|
526
|
+
expect(:any_of) do
|
527
|
+
expect_a(:scalar) .to_be_folder
|
528
|
+
or_also
|
529
|
+
expect_a(:list) .not_empty do
|
530
|
+
each do
|
531
|
+
expect_a(:scalar) .to_be_folder
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
```
|
537
|
+
|
538
|
+
## Defining custom expectations
|
539
|
+
|
540
|
+
So far we only used the expectations defined by the `json-spec` framework. But more likely we will want to define our custom expectations. There are several reasons for that. Different APIs use different formats, or in some contexts we may want to use more intention revealing expectation names, to name a few.
|
541
|
+
|
542
|
+
There are many ways to define new expectations, let's go through them:
|
543
|
+
|
544
|
+
- Delegate the new expectation to an existing one.
|
545
|
+
|
546
|
+
Suppose we want to check if a value is equal to 42. We can achieve that by doing
|
547
|
+
|
548
|
+
```ruby
|
549
|
+
json_spec .to_be_equal_to(42)
|
550
|
+
```
|
551
|
+
|
552
|
+
But if we want a more meaningful expectation name, we can add it to the [ExpectationsLibrary](lib/cabeza-de-termo/json-spec/expectations-library/expectations-library.rb).
|
553
|
+
|
554
|
+
Here's an example of doing so:
|
555
|
+
|
556
|
+
```ruby
|
557
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
558
|
+
expectations do
|
559
|
+
define :to_be_the_answer_to_life_the_universe_and_everything do
|
560
|
+
expecting :to_be_equal_to, 42
|
561
|
+
message "Nop, this is not the answer to life, the universe and everything."
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
```
|
566
|
+
|
567
|
+
```ruby
|
568
|
+
json_spec .to_be_the_answer_to_life_the_universe_and_everything
|
569
|
+
# is now equivalent to:
|
570
|
+
json_spec .to_be_equal_to(42)
|
571
|
+
```
|
572
|
+
|
573
|
+
Here's another interesting example of defining new expectations by delegation:
|
574
|
+
|
575
|
+
```ruby
|
576
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
577
|
+
expectations do
|
578
|
+
define :to_be_date do
|
579
|
+
expecting :to_match, /^\d\d\d\d-\d\d-\d\d$/
|
580
|
+
message "Not a valid date format."
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
```
|
585
|
+
|
586
|
+
This framework does not include a `to_be_date` expectation because it varies a lot from one API to another, but you can easyly add the one that suits your needs.
|
587
|
+
|
588
|
+
Now compare using in your specs:
|
589
|
+
|
590
|
+
```ruby
|
591
|
+
json_spec .to_match(/^\d\d\d\d-\d\d-\d\d$/)
|
592
|
+
# vs
|
593
|
+
json_spec .to_be_date
|
594
|
+
```
|
595
|
+
|
596
|
+
- Negate an existing expectation.
|
597
|
+
|
598
|
+
If you want to expect that something is not what another expectation asserts, do:
|
599
|
+
|
600
|
+
```ruby
|
601
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
602
|
+
expectations do
|
603
|
+
define :not_date do
|
604
|
+
negating :to_be_date
|
605
|
+
message "Expected an invalid date, got a valid one."
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
```
|
610
|
+
|
611
|
+
- Chain several existing expectations:
|
612
|
+
|
613
|
+
```ruby
|
614
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
615
|
+
expectations do
|
616
|
+
define :to_be_defined do
|
617
|
+
expecting_all_of :to_exist
|
618
|
+
and_also :not_null
|
619
|
+
|
620
|
+
message "Failed asserting that the field '<%= field %>' with value = '<%= format value %>' is defined."
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
```
|
625
|
+
|
626
|
+
- Expect one existing expectation among several ones:
|
627
|
+
|
628
|
+
```ruby
|
629
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
630
|
+
expectations do
|
631
|
+
define :to_be_accessor do
|
632
|
+
expecting_any_of :to_be_getter
|
633
|
+
or_also :to_be_setter
|
634
|
+
|
635
|
+
message "The value is not an accessor string."
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
```
|
640
|
+
|
641
|
+
So far we only composed existing expectations. Now we are going to define new ones.
|
642
|
+
|
643
|
+
- Define a new expectation with a closure:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
647
|
+
expectations do
|
648
|
+
define :to_be_greater_than do
|
649
|
+
with_block { |value_holder, expected_value| expected_value < value_holder.value }
|
650
|
+
message "<%= value %> is not greater than <%= expectation.args[0] %>."
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
654
|
+
```
|
655
|
+
- Or finally, if your expectation is more complex, create a class for it:
|
656
|
+
|
657
|
+
```ruby
|
658
|
+
require 'cabeza-de-termo/json-spec/expectations/expectation'
|
659
|
+
|
660
|
+
class IsSomeComplexStuff < CabezaDeTermo::JsonSpec::Expectation
|
661
|
+
def is_satisfied_by?(value_holder)
|
662
|
+
# Check stuff and answer true or false.
|
663
|
+
# You can get the inspected value with value_holder.value
|
664
|
+
end
|
665
|
+
end
|
666
|
+
```
|
667
|
+
|
668
|
+
and then define the expectation in the ExpectationsLibrary
|
669
|
+
|
670
|
+
```ruby
|
671
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
672
|
+
expectations do
|
673
|
+
define :to_be_some_complex_stuff do
|
674
|
+
with_class IsSomeComplexStuff
|
675
|
+
message "<%= value %> is not a ComplexStuff."
|
676
|
+
end
|
677
|
+
end
|
678
|
+
end
|
679
|
+
```
|
680
|
+
|
681
|
+
If you want to run this example, see the [fifth example](examples/example-5-custom-expectations.rb) in the `examples/` folder.
|
682
|
+
|
683
|
+
## Defining custom messages
|
684
|
+
|
685
|
+
We saw how we can define new Expectations with their own validation error message. But it would be nice to be able to change the validation error messages for the existing expectations as well.
|
686
|
+
|
687
|
+
We can do that at 2 different scopes, just like when we defined default expectations.
|
688
|
+
|
689
|
+
To override the validation messages globally, use
|
690
|
+
|
691
|
+
```ruby
|
692
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
693
|
+
expectations do
|
694
|
+
define :to_be_defined do
|
695
|
+
message "If the question is to be or not to be, this value has chosen not to be. Alas, poor <%= field %>. My ValidationError rises at it."
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|
699
|
+
```
|
700
|
+
|
701
|
+
If we want to override the message only for a single `json-spec`, do
|
702
|
+
|
703
|
+
```ruby
|
704
|
+
json_spec.define do
|
705
|
+
expectations do
|
706
|
+
define :to_be_integer do
|
707
|
+
message "An integer, a integer! My <%= field %> for an integer!"
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
```
|
712
|
+
|
713
|
+
You have to admit that these custom messages, although they are in a slang so boring and outdated that will get you to sleep in, like, 10 minutes, have a lot more of what we might call `poetic flight` than the default ones.
|
714
|
+
|
715
|
+
Just like when defining new Expectations, we have several ways to define custom messages. Lets walk through them:
|
716
|
+
|
717
|
+
- Using a [ErbMessageFormatter](lib/cabeza-de-termo/json-spec/message-formatters/erb-message-formatter.rb) object
|
718
|
+
|
719
|
+
```ruby
|
720
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
721
|
+
expectations do
|
722
|
+
define :to_be_integer do
|
723
|
+
message "An integer, a integer! My <%= field %> for an integer!"
|
724
|
+
end
|
725
|
+
end
|
726
|
+
end
|
727
|
+
```
|
728
|
+
|
729
|
+
This is the simpliest way, and it should be enough most of the times. You can reference :value_holder, :value, :accessors_chain, :field and :expectation objects from within a erb block: `<%= field %>`.
|
730
|
+
|
731
|
+
- Using a [BlockMessageFormatter](lib/cabeza-de-termo/json-spec/message-formatters/block-message-formatter.rb) object
|
732
|
+
|
733
|
+
```ruby
|
734
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
735
|
+
expectations do
|
736
|
+
define :to_be_integer do
|
737
|
+
message_block { |expectation, value_holder| "No a valid integer." }
|
738
|
+
end
|
739
|
+
end
|
740
|
+
end
|
741
|
+
```
|
742
|
+
|
743
|
+
- Using you own [MessageFormatter](lib/cabeza-de-termo/json-spec/message-formatters/message-formatter.rb) subclass
|
744
|
+
|
745
|
+
Define a new class with a `:message_on(expectation, value_holder)` method
|
746
|
+
|
747
|
+
```ruby
|
748
|
+
class MyCustomMessageFormatter
|
749
|
+
def message_on(expectation, value_holder)
|
750
|
+
"Nop!"
|
751
|
+
end
|
752
|
+
end
|
753
|
+
```
|
754
|
+
|
755
|
+
and then override the message
|
756
|
+
|
757
|
+
```ruby
|
758
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary.define do
|
759
|
+
expectations do
|
760
|
+
define :to_be_integer do
|
761
|
+
message_formatter MyCustomMessageFormatter.new
|
762
|
+
end
|
763
|
+
end
|
764
|
+
end
|
765
|
+
```
|
766
|
+
|
767
|
+
If you want to run the custom messages example, see the [sixth example](examples/example-6-custom-messages.rb) in the `examples/` folder.
|
768
|
+
|
769
|
+
## Conditional expectations and expression modifiers
|
770
|
+
|
771
|
+
So far we only talked about Expectations. However, sometimes expectations are not enough. For instance, sometimes you may want to decide whether to keep running expectations or not, but without failing. An example of that are the `:can_be_something` statements. `:can_be_absent` states that a field may be missing and it's ok, but if it is present we want to keep running expectations on that expression.
|
772
|
+
In the `json-spec` framework, each expression does not hold a collection of expectations, but an [ExpectationsRunner](lib/cabeza-de-termo/json-spec/expectations/runner/expectations-runner.rb) instead.
|
773
|
+
If you plug your own [AbstractExpectationsRunner](lib/cabeza-de-termo/json-spec/expectations/runner/abstract-expectations-runner.rb) you can alter the execution flow of the expectations for that expression.
|
774
|
+
|
775
|
+
And how would you plug an ExpectationsRunner in a json_expression?
|
776
|
+
|
777
|
+
With the help of an [ExpressionModifier](lib/cabeza-de-termo/json-spec/modifiers/expression-modifier.rb), which you can add to the [ExpectationsLibrary](lib/cabeza-de-termo/json-spec/expectations-library/expectations-library.rb) using its `:define` method, just like with the custom expectations.
|
778
|
+
|
779
|
+
Or perhaps some expectation needs to remove other expectations for an expression to make sense.
|
780
|
+
In that case you can also use your own ExpressionModifier.
|
781
|
+
|
782
|
+
This sounds more complicated than it actually is. Check the [CanBeNullModifier](lib/cabeza-de-termo/json-spec/modifiers/can-be-null-modifier.rb) to see a real example and see that it's actually quite easy to do weird stuff on the expressions and expectations execution flow.
|
783
|
+
|
784
|
+
## Putting it all together
|
785
|
+
|
786
|
+
Ok, so we can define default expectations for each expression, add new custom expectations, add new custom modifiers and replace the default expectation messages without the need of subclassing any existing class of the framework. That is really handy for simple validations. But what if we have done a nice and complete set of expectations and messages that we want to keep together to use it in several places?
|
787
|
+
|
788
|
+
In that case, it is a good idea to bundle it all together in one place.
|
789
|
+
|
790
|
+
That place can be the [LibraryInitializer](lib/cabeza-de-termo/json-spec/expectations-library/initializers/library-initializer.rb).
|
791
|
+
|
792
|
+
Create you own class that implements the [LibraryInitializer](lib/cabeza-de-termo/json-spec/expectations-library/initializers/library-initializer.rb) protocol, and there create a new and fully configured [ExpectationsLibrary](lib/cabeza-de-termo/json-spec/expectations-library/expectations-library.rb).
|
793
|
+
|
794
|
+
Something like this:
|
795
|
+
|
796
|
+
```ruby
|
797
|
+
class ComposerLibraryInitializer
|
798
|
+
def new_library()
|
799
|
+
initialize_library CabezaDeTermo::JsonSpec::DefaultLibraryInitializer.new.new_library
|
800
|
+
end
|
801
|
+
|
802
|
+
def initialize_library(library)
|
803
|
+
# add more custom stuff to the library here
|
804
|
+
|
805
|
+
library
|
806
|
+
end
|
807
|
+
end
|
808
|
+
```
|
809
|
+
|
810
|
+
and now plug the initializer into the ExpectationsLibrary at your application bootstrap with
|
811
|
+
|
812
|
+
```ruby
|
813
|
+
CabezaDeTermo::JsonSpec::ExpectationsLibrary
|
814
|
+
.set_default_library_initializer( ComposerLibraryInitializer.new )
|
815
|
+
```
|
816
|
+
|
817
|
+
and that's it, now you can use the ExpectationsLibrary with all your custom additions.
|
818
|
+
|
819
|
+
To see how all the pieces fitted together, check and run the [seventh example](examples/example-7-full-example.rb) in the `examples/` folder.
|
820
|
+
|
821
|
+
## Inspecting the expectations
|
822
|
+
|
823
|
+
With the default expectations and expression modifiers, it is quite sure that sooner or later you will want to see what expectations are actually set to each expression in a json_spec.
|
824
|
+
If you need to debug the expectations, run
|
825
|
+
|
826
|
+
```ruby
|
827
|
+
puts json_spec.explain
|
828
|
+
```
|
829
|
+
|
830
|
+
and will get something like this
|
831
|
+
|
832
|
+
```ruby
|
833
|
+
{
|
834
|
+
"name":
|
835
|
+
anything .not_blank()
|
836
|
+
"type":
|
837
|
+
anything .not_blank()
|
838
|
+
"description":
|
839
|
+
anything .not_blank()
|
840
|
+
"keywords":
|
841
|
+
[
|
842
|
+
scalar .not_blank()
|
843
|
+
]
|
844
|
+
if present
|
845
|
+
.to_be_list(Array) .not_empty()
|
846
|
+
"homepage":
|
847
|
+
anything .to_be_url()
|
848
|
+
"license":
|
849
|
+
anything .to_be_valid_lincense(Apache-2.0, BSD-2-Clause, BSD-3-Clause, BSD-4-Clause, GPL-2.0, GPL-2.0+, GPL-3.0, GPL-3.0+, LGPL-2.1, LGPL-2.1+, LGPL-3.0, LGPL-3.0+, MIT)
|
850
|
+
"authors":
|
851
|
+
[
|
852
|
+
{
|
853
|
+
"name":
|
854
|
+
anything .not_blank()
|
855
|
+
"email":
|
856
|
+
anything .to_be_email()
|
857
|
+
} .to_be_object(Hash)
|
858
|
+
] .to_be_list(Array) .not_empty()
|
859
|
+
"require":
|
860
|
+
{
|
861
|
+
each field
|
862
|
+
name .not_blank()
|
863
|
+
value
|
864
|
+
scalar .to_be_version()
|
865
|
+
}
|
866
|
+
if present
|
867
|
+
.to_be_object(Hash)
|
868
|
+
"require-dev":
|
869
|
+
{
|
870
|
+
each field
|
871
|
+
name .not_blank()
|
872
|
+
value
|
873
|
+
scalar .to_be_version()
|
874
|
+
}
|
875
|
+
if present
|
876
|
+
.to_be_object(Hash)
|
877
|
+
"autoload":
|
878
|
+
{
|
879
|
+
"psr-0":
|
880
|
+
{
|
881
|
+
each field
|
882
|
+
name .to_be_string(String)
|
883
|
+
value
|
884
|
+
any of
|
885
|
+
scalar .to_be_folder()
|
886
|
+
or
|
887
|
+
[
|
888
|
+
scalar .to_be_folder()
|
889
|
+
] .to_be_list(Array) .not_empty()
|
890
|
+
}
|
891
|
+
if present
|
892
|
+
.to_be_object(Hash)
|
893
|
+
"psr-4":
|
894
|
+
{
|
895
|
+
each field
|
896
|
+
name .to_be_psr4_key()
|
897
|
+
value
|
898
|
+
any of
|
899
|
+
scalar .to_be_folder()
|
900
|
+
or
|
901
|
+
[
|
902
|
+
scalar .to_be_folder()
|
903
|
+
] .to_be_list(Array) .not_empty()
|
904
|
+
}
|
905
|
+
if present
|
906
|
+
.to_be_object(Hash)
|
907
|
+
"classmap":
|
908
|
+
[
|
909
|
+
]
|
910
|
+
if present
|
911
|
+
.to_be_list(Array) .not_empty()
|
912
|
+
"files":
|
913
|
+
[
|
914
|
+
]
|
915
|
+
if present
|
916
|
+
.to_be_list(Array) .not_empty()
|
917
|
+
} .to_be_object(Hash)
|
918
|
+
} .to_be_object(Hash)
|
919
|
+
```
|
920
|
+
|
921
|
+
## Development environment
|
922
|
+
|
923
|
+
So, you are too lazy to setup the development environment for this project. Yeah, I feel you. I am too.
|
924
|
+
|
925
|
+
Anyways, you can use the Vagrant configuration for that. To do so:
|
926
|
+
|
927
|
+
* Install [VirtualBox](https://www.virtualbox.org/)
|
928
|
+
* Install [Vagrant](https://www.vagrantup.com/)
|
929
|
+
* `git clone --recursive git@github.com:mrubi/ruby-json-spec.git`
|
930
|
+
* `cd ruby-json-spec/cachivache`
|
931
|
+
* `vagrant up`
|
932
|
+
|
933
|
+
and that will install all the necessary things to run the tests and examples in this project.
|
934
|
+
|
935
|
+
Then to start playing around with the code, do
|
936
|
+
|
937
|
+
* `vagrant ssh`
|
938
|
+
* `cd src/json-spec`
|
939
|
+
|
940
|
+
and that's it. You have a fully prepared, ready to use development environment.
|
941
|
+
|
942
|
+
# Running the tests
|
943
|
+
|
944
|
+
* `rake test`
|
945
|
+
|
946
|
+
## Development
|
947
|
+
|
948
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
949
|
+
|
950
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
951
|
+
|
952
|
+
## Contributing
|
953
|
+
|
954
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/json-spec.
|
955
|
+
|
956
|
+
|
957
|
+
## License
|
958
|
+
|
959
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
960
|
+
|