rack-schema 0.5.0
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +7 -0
- data/lib/rack/schema.rb +83 -0
- data/lib/rack/schema/version.rb +5 -0
- data/rack-schema.gemspec +33 -0
- data/spec/link_header_spec.rb +231 -0
- data/spec/rack_schema_spec.rb +51 -0
- data/spec/schemas/auction.json +9 -0
- data/spec/schemas/object.json +5 -0
- data/spec/schemas/response.json +14 -0
- data/spec/schemas/widget.json +9 -0
- data/spec/spec_helper.rb +59 -0
- metadata +246 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kyle Hargraves
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Rack::Schema
|
2
|
+
|
3
|
+
Validate your application's responses against [JSON Schemas][json-schema].
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'rack-schema'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rack-schema
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
Mount `Rack::Schema` as middleware in one of the normal manners:
|
21
|
+
|
22
|
+
~~~~ruby
|
23
|
+
# using config.ru:
|
24
|
+
use Rack::Schema
|
25
|
+
run MyApp
|
26
|
+
|
27
|
+
# or application.rb:
|
28
|
+
config.middleware.use Rack::Schema
|
29
|
+
~~~~
|
30
|
+
|
31
|
+
Your application can now return an HTTP [Link header][link-header]
|
32
|
+
with a `rel` attribute value of `describedby`, and `Rack::Schema` will
|
33
|
+
automatically attempt to validate responses against the specified
|
34
|
+
schema (using the [json-schema gem][hoxworth]). An example `Link`
|
35
|
+
header:
|
36
|
+
|
37
|
+
Link: <http://example.com/schemas/response>; rel="describedby"
|
38
|
+
|
39
|
+
If your schema applies only to a part of the JSON response, you can
|
40
|
+
use the `anchor` attribute to specify a JSON path to the relevant value:
|
41
|
+
|
42
|
+
Link: <http://example.com/schemas/widget>; rel="describedby"; anchor="#/widget"
|
43
|
+
|
44
|
+
This is actually a mis-use of the `anchor` attribute, which would
|
45
|
+
typically be used to specify an anchor within the *linked* document,
|
46
|
+
rather than the document being described. JSON schemas already support
|
47
|
+
the use of the hash fragment on its URI, however, so I've
|
48
|
+
re-appropriated it. Suggestions for a more compliant tactic are
|
49
|
+
welcome.
|
50
|
+
|
51
|
+
If your response is actually a collection of objects that should all
|
52
|
+
validate against the same schema, use the `collection` attribute:
|
53
|
+
|
54
|
+
# Assert that the response is an array, and each object within it is a valid widget.
|
55
|
+
Link: <http://example.com/schemas/widget>; rel="describedby"; collection="collection"
|
56
|
+
|
57
|
+
# Assert that the object at '#/widgets' is an array, and each object within it is a valid widget.
|
58
|
+
Link: <http://example.com/schemas/widget>; rel="describedby"; anchor="#/widgets"; collection="collection"
|
59
|
+
|
60
|
+
If the `Link` header contains multiple applicable links, they will
|
61
|
+
all be used to validate the response:
|
62
|
+
|
63
|
+
# Assert that '#/teams' is an array of valid teams, and '#/score' is a valid score.
|
64
|
+
Link: <http://example.com/schemas/team>; rel="describedby"; anchor="#/teams"; collection="collection",
|
65
|
+
<http://example.com/schemas/score>; rel="describedby"; anchor="#/score"
|
66
|
+
|
67
|
+
## Configuration
|
68
|
+
By default, `rack-schema` will also instruct the validator to validate
|
69
|
+
your schema itself *as* a schema. To disable that behavior:
|
70
|
+
|
71
|
+
~~~~ruby
|
72
|
+
use Rack::Schema, validate_schemas: false
|
73
|
+
~~~~
|
74
|
+
|
75
|
+
If you are running the `rack-schema` response validator in a
|
76
|
+
production environment -- which you probably *shouldn't* be doing --
|
77
|
+
you don't want to actually expose the `describedby` link header
|
78
|
+
entries to the world, you can tell `rack-schema` to remove them from
|
79
|
+
the responses after using them:
|
80
|
+
|
81
|
+
~~~~ruby
|
82
|
+
use Rack::Schema, swallow_links: true
|
83
|
+
~~~~
|
84
|
+
|
85
|
+
With `swallow_links` on, only the *describedby* links will be removed;
|
86
|
+
your pagination or similar links will not be disturbed.
|
87
|
+
|
88
|
+
|
89
|
+
## Potential Features?
|
90
|
+
|
91
|
+
1. Validate incoming JSON bodies, but I just don't need that right now.
|
92
|
+
And it's unclear how we'd determine what schemas to use, or what we'd
|
93
|
+
do with the errors.
|
94
|
+
|
95
|
+
## See Also
|
96
|
+
|
97
|
+
1. [HTTP Link Header][link-header]
|
98
|
+
2. [json-schema gem][hoxworth]
|
99
|
+
|
100
|
+
[json-schema]: http://json-schema.org
|
101
|
+
[link-header]: http://tools.ietf.org/html/rfc5988#section-5
|
102
|
+
[hoxworth]: https://github.com/hoxworth/json-schema
|
data/Rakefile
ADDED
data/lib/rack/schema.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require "rack/schema/version"
|
2
|
+
require "json-schema"
|
3
|
+
require "link_header"
|
4
|
+
require "multi_json"
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
class Schema
|
8
|
+
ValidationError = Class.new(StandardError)
|
9
|
+
|
10
|
+
def initialize(app, options = {}, &handler)
|
11
|
+
@app = app
|
12
|
+
|
13
|
+
@handler = handler
|
14
|
+
@handler ||= proc { |errors, env, (status, headers, body)|
|
15
|
+
json = ''
|
16
|
+
body.each { |s| json.concat s }
|
17
|
+
raise ValidationError.new({ errors: errors, body: json }) if errors.any?
|
18
|
+
}
|
19
|
+
|
20
|
+
@options = {
|
21
|
+
validate_schemas: true,
|
22
|
+
swallow_links: false
|
23
|
+
}.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
status, headers, body = @app.call(env)
|
28
|
+
|
29
|
+
link_header = LinkHeader.parse(headers['Link'])
|
30
|
+
schema_links = link_header.links.select do |link|
|
31
|
+
link.attrs['rel'] == 'describedby'
|
32
|
+
end
|
33
|
+
|
34
|
+
errors = validate(body, schema_links)
|
35
|
+
swallow(headers, link_header) if swallow_links?
|
36
|
+
response = [status, headers, body]
|
37
|
+
@handler.call(errors, env, response) || response
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def validate(body, schema_links)
|
43
|
+
schema_links.each_with_object [] do |link, acc|
|
44
|
+
json = at_anchor(body, link.attrs['anchor'])
|
45
|
+
|
46
|
+
errs = JSON::Validator.fully_validate link.href, json, {
|
47
|
+
validate_schemas: @options[:validate_schemas],
|
48
|
+
list: link.attrs.key?('collection')
|
49
|
+
}
|
50
|
+
|
51
|
+
acc.push [link.to_s, errs] if errs.any?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def swallow_links?
|
56
|
+
@options[:swallow_links] == true
|
57
|
+
end
|
58
|
+
|
59
|
+
def swallow(headers, link_header)
|
60
|
+
link_header.links.reject! { |link| link.attrs['rel'] == 'describedby' }
|
61
|
+
if link_header.links.any?
|
62
|
+
headers['Link'] = link_header.to_s
|
63
|
+
else
|
64
|
+
headers.delete 'Link'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def at_anchor(body, anchor)
|
69
|
+
flat = ''
|
70
|
+
body.each { |s| flat.concat s }
|
71
|
+
|
72
|
+
return flat if anchor.nil? || anchor == '#' || anchor == '#/'
|
73
|
+
|
74
|
+
fragments = anchor.sub(/\A#\//, '').split('/')
|
75
|
+
fragments.reduce MultiJson.load(flat) do |value, fragment|
|
76
|
+
case value
|
77
|
+
when Hash then value.fetch(fragment, nil)
|
78
|
+
when Array then value.fetch(fragment.to_i, nil)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/rack-schema.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rack/schema/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rack-schema"
|
8
|
+
spec.version = Rack::Schema::VERSION
|
9
|
+
spec.authors = ["Kyle Hargraves"]
|
10
|
+
spec.email = ["pd@krh.me"]
|
11
|
+
spec.description = %q{Validate rack responses against schema named in the Link header}
|
12
|
+
spec.summary = %q{Allows you to strictly validate each of your application's API responses against a declared JSON schema.}
|
13
|
+
spec.homepage = "http://github.com/pd/rack-schema"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rack"
|
22
|
+
spec.add_dependency "multi_json"
|
23
|
+
spec.add_dependency "json-schema"
|
24
|
+
spec.add_dependency "link_header"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "oj"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "rack-test"
|
30
|
+
spec.add_development_dependency "rspec"
|
31
|
+
spec.add_development_dependency "simplecov"
|
32
|
+
spec.add_development_dependency "pry"
|
33
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Schema, 'URIs, anchors, collections oh my!' do
|
4
|
+
Invalid = Class.new(StandardError)
|
5
|
+
|
6
|
+
def app
|
7
|
+
Rack::Builder.new do
|
8
|
+
use Rack::Schema do |errors, env, (status, headers, body)|
|
9
|
+
raise Invalid if errors.any?
|
10
|
+
headers['X-Schema'] = 'valid'
|
11
|
+
[status, headers, body]
|
12
|
+
end
|
13
|
+
|
14
|
+
run SpecHelpers::EchoApp
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_valid!
|
19
|
+
expect(last_response.headers['X-Schema']).to eq('valid')
|
20
|
+
end
|
21
|
+
|
22
|
+
def expect_invalid(&block)
|
23
|
+
expect(&block).to raise_error(Invalid)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'invalid JSON body' do
|
27
|
+
specify 'ignored without Link header' do
|
28
|
+
echo headers, '!!!'
|
29
|
+
assert_valid!
|
30
|
+
|
31
|
+
echo headers, nil
|
32
|
+
assert_valid!
|
33
|
+
end
|
34
|
+
|
35
|
+
specify 'with Link header present, we might have to parse the body' do
|
36
|
+
headers['Link'] = described_by schema_uri('object')
|
37
|
+
expect_invalid { echo headers, '!!!' }
|
38
|
+
expect_invalid { echo headers, nil }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'validate entire body' do
|
43
|
+
before do
|
44
|
+
headers['Link'] = described_by schema_uri('object')
|
45
|
+
end
|
46
|
+
|
47
|
+
specify 'pass' do
|
48
|
+
echo headers, {}
|
49
|
+
assert_valid!
|
50
|
+
end
|
51
|
+
|
52
|
+
specify 'fail by type' do
|
53
|
+
expect_invalid { echo headers, 'a string' }
|
54
|
+
expect_invalid { echo headers, [{}, {}] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'validate at anchor' do
|
59
|
+
before do
|
60
|
+
headers['Link'] = described_by schema_uri('widget'), '#/widget'
|
61
|
+
end
|
62
|
+
|
63
|
+
specify 'pass at anchor' do
|
64
|
+
echo headers, { 'widget' => { 'name' => 'foo' } }
|
65
|
+
assert_valid!
|
66
|
+
end
|
67
|
+
|
68
|
+
specify 'fail; object was at root instead of anchor' do
|
69
|
+
expect_invalid do
|
70
|
+
echo headers, { 'name' => 'foo' }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
specify 'fail; no such anchor' do
|
75
|
+
expect_invalid do
|
76
|
+
echo headers, { 'Widget' => { 'name' => 'foo' } }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
specify 'fail; anchor pointed to collection' do
|
81
|
+
expect_invalid do
|
82
|
+
echo headers, { 'widget' => [{'name' => 'foo'}, {'name' => 'bar'}] }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'validate at array element' do
|
88
|
+
before do
|
89
|
+
headers['Link'] = described_by schema_uri('widget'), '#/1'
|
90
|
+
end
|
91
|
+
|
92
|
+
specify 'pass' do
|
93
|
+
echo headers, ['anything', {'name' => 'real widget'}]
|
94
|
+
assert_valid!
|
95
|
+
end
|
96
|
+
|
97
|
+
specify 'fail; invalid' do
|
98
|
+
expect_invalid do
|
99
|
+
echo headers, ['anything', {'nope' => 'not okay'}]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
specify 'fail; not an array' do
|
104
|
+
expect_invalid do
|
105
|
+
echo headers, {'some' => 'object'}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
specify 'fail; wrong element' do
|
110
|
+
expect_invalid do
|
111
|
+
echo headers, [{'name' => 'widget'}, 'oops, backwards!']
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
['#', '#/'].each do |anchor|
|
117
|
+
context "validate at anchor #{anchor.inspect}" do
|
118
|
+
before do
|
119
|
+
headers['Link'] = described_by schema_uri('widget'), anchor
|
120
|
+
end
|
121
|
+
|
122
|
+
specify 'pass' do
|
123
|
+
echo headers, { 'name' => 'foo' }
|
124
|
+
assert_valid!
|
125
|
+
end
|
126
|
+
|
127
|
+
specify 'fail' do
|
128
|
+
expect_invalid do
|
129
|
+
echo headers, { 'name' => true }
|
130
|
+
end
|
131
|
+
|
132
|
+
expect_invalid do
|
133
|
+
echo headers, { 'widget' => { 'name' => 'foo' } }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'validate collection' do
|
140
|
+
before do
|
141
|
+
headers['Link'] = described_by schema_uri('widget'), nil, :collection
|
142
|
+
end
|
143
|
+
|
144
|
+
specify 'pass' do
|
145
|
+
echo headers, [{'name' => 'foo'}, {'name' => 'bar'}]
|
146
|
+
assert_valid!
|
147
|
+
end
|
148
|
+
|
149
|
+
specify 'pass; empty array' do
|
150
|
+
echo headers, []
|
151
|
+
assert_valid!
|
152
|
+
end
|
153
|
+
|
154
|
+
specify 'fail; not an array' do
|
155
|
+
expect_invalid do
|
156
|
+
echo headers, {'name' => 'foo'}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
specify 'fail; invalid entry' do
|
161
|
+
expect_invalid do
|
162
|
+
echo headers, [{'name' => 'foo'}, {'invalid' => true}]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'validate collection at anchor' do
|
168
|
+
before do
|
169
|
+
headers['Link'] = described_by schema_uri('widget'), '#/widgets', :collection
|
170
|
+
end
|
171
|
+
|
172
|
+
specify 'pass' do
|
173
|
+
echo headers, { 'widgets' => [{'name' => 'foo'}, {'name' => 'bar'}] }
|
174
|
+
assert_valid!
|
175
|
+
end
|
176
|
+
|
177
|
+
specify 'pass; empty array' do
|
178
|
+
echo headers, { 'widgets' => [] }
|
179
|
+
assert_valid!
|
180
|
+
end
|
181
|
+
|
182
|
+
specify 'fail; no such anchor' do
|
183
|
+
expect_invalid do
|
184
|
+
echo headers, { 'Widgets' => [{'name' => 'foo'}] }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
specify 'fail; invalid entries' do
|
189
|
+
expect_invalid do
|
190
|
+
echo headers, { 'widgets' => [{'bogus' => 'object'}] }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'multiple schemas' do
|
196
|
+
before do
|
197
|
+
headers['Link'] = [described_by(schema_uri('response'), '#'),
|
198
|
+
described_by(schema_uri('widget'), '#/widgets', :collection),
|
199
|
+
described_by(schema_uri('auction'), '#/auction')].join(", ")
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:body) do
|
203
|
+
{ 'links' => [{'rel' => 'self'}],
|
204
|
+
'widgets' => [{'name' => 'foo'}],
|
205
|
+
'auction' => {'bids' => 10, 'price' => 100}
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
specify 'pass all' do
|
210
|
+
echo headers, body
|
211
|
+
assert_valid!
|
212
|
+
end
|
213
|
+
|
214
|
+
specify 'fail all' do
|
215
|
+
expect_invalid do
|
216
|
+
echo headers, {}
|
217
|
+
end
|
218
|
+
|
219
|
+
expect_invalid do
|
220
|
+
echo headers, {'links' => 'oops', 'widgets' => ['so', 'wrong'], 'auction' => nil}
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
specify 'fail one' do
|
225
|
+
body['links'] = []
|
226
|
+
expect_invalid do
|
227
|
+
echo headers, body
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Schema do
|
4
|
+
context 'default handler' do
|
5
|
+
def app
|
6
|
+
Rack::Builder.new do
|
7
|
+
use Rack::Schema
|
8
|
+
run SpecHelpers::EchoApp
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'raises Rack::Schema::ValidationError if there were errors' do
|
13
|
+
headers['Link'] = described_by schema_uri('widget')
|
14
|
+
expect {
|
15
|
+
echo headers, {}
|
16
|
+
}.to raise_error(Rack::Schema::ValidationError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'otherwise does not alter the response' do
|
20
|
+
headers['Link'] = described_by schema_uri('widget')
|
21
|
+
echo headers, {'name' => 'foo'}
|
22
|
+
|
23
|
+
expect(last_response.headers).to have_key('Link')
|
24
|
+
expect(last_response.body).to eql(MultiJson.dump({'name' => 'foo'}))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'swallow_links: true' do
|
29
|
+
def app
|
30
|
+
Rack::Builder.new do
|
31
|
+
use Rack::Schema, swallow_links: true
|
32
|
+
run SpecHelpers::EchoApp
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'removes only rel=describedby Links' do
|
37
|
+
headers['Link'] = [described_by(schema_uri('widget')),
|
38
|
+
'<http://another/link>; rel="next"'].join(", ")
|
39
|
+
echo headers, { 'name' => 'foo' }
|
40
|
+
|
41
|
+
expect(last_response.headers).to have_key('Link')
|
42
|
+
expect(last_response.headers['Link']).to eql('<http://another/link>; rel="next"')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'removes the Link header if empty' do
|
46
|
+
headers['Link'] = described_by schema_uri('widget')
|
47
|
+
echo headers, { 'name' => 'foo' }
|
48
|
+
expect(last_response.headers).not_to have_key('Link')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"title": "Common schema for service JSON responses",
|
4
|
+
|
5
|
+
"properties": {
|
6
|
+
"links": {
|
7
|
+
"type": "array",
|
8
|
+
"items": {
|
9
|
+
"properties": { "rel": { "type": "string" } }
|
10
|
+
},
|
11
|
+
"minItems": 1
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'rack/test'
|
8
|
+
require 'rack/schema'
|
9
|
+
require 'pry'
|
10
|
+
|
11
|
+
# `json' needs to be here because simplecov assumes that if
|
12
|
+
# ::JSON is defined, it means we have ruby's JSON loaded; otherwise,
|
13
|
+
# they use MultiJson. silly.
|
14
|
+
require "oj"
|
15
|
+
require "json"
|
16
|
+
|
17
|
+
module SpecHelpers
|
18
|
+
def echo(headers, body, status = 200)
|
19
|
+
env = {
|
20
|
+
'echo.body' => MultiJson.dump(body),
|
21
|
+
'echo.headers' => headers,
|
22
|
+
'echo.status' => status
|
23
|
+
}
|
24
|
+
get '/', {}, env
|
25
|
+
end
|
26
|
+
|
27
|
+
def headers
|
28
|
+
@headers ||= {
|
29
|
+
'Content-Type' => 'application/json'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def schema_uri(name)
|
34
|
+
"file://#{schema_file(name)}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def schema_file(name)
|
38
|
+
File.expand_path("../schemas/#{name}.json", __FILE__)
|
39
|
+
end
|
40
|
+
|
41
|
+
def described_by(uri, anchor = nil, collection = nil)
|
42
|
+
header = "<#{uri}>; rel=\"describedby\""
|
43
|
+
header.concat "; anchor=\"#{anchor}\"" if anchor
|
44
|
+
header.concat "; collection=\"collection\"" if collection
|
45
|
+
header
|
46
|
+
end
|
47
|
+
|
48
|
+
module EchoApp
|
49
|
+
def self.call(env)
|
50
|
+
body = [env['echo.body']] # body should respond to :each
|
51
|
+
[env['echo.status'], env['echo.headers'], body]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
RSpec.configure do |config|
|
57
|
+
config.include Rack::Test::Methods
|
58
|
+
config.include SpecHelpers
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-schema
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kyle Hargraves
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json-schema
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: link_header
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.3'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.3'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: oj
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rack-test
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rspec
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: simplecov
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: pry
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
description: Validate rack responses against schema named in the Link header
|
191
|
+
email:
|
192
|
+
- pd@krh.me
|
193
|
+
executables: []
|
194
|
+
extensions: []
|
195
|
+
extra_rdoc_files: []
|
196
|
+
files:
|
197
|
+
- .gitignore
|
198
|
+
- Gemfile
|
199
|
+
- LICENSE.txt
|
200
|
+
- README.md
|
201
|
+
- Rakefile
|
202
|
+
- lib/rack/schema.rb
|
203
|
+
- lib/rack/schema/version.rb
|
204
|
+
- rack-schema.gemspec
|
205
|
+
- spec/link_header_spec.rb
|
206
|
+
- spec/rack_schema_spec.rb
|
207
|
+
- spec/schemas/auction.json
|
208
|
+
- spec/schemas/object.json
|
209
|
+
- spec/schemas/response.json
|
210
|
+
- spec/schemas/widget.json
|
211
|
+
- spec/spec_helper.rb
|
212
|
+
homepage: http://github.com/pd/rack-schema
|
213
|
+
licenses:
|
214
|
+
- MIT
|
215
|
+
post_install_message:
|
216
|
+
rdoc_options: []
|
217
|
+
require_paths:
|
218
|
+
- lib
|
219
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
220
|
+
none: false
|
221
|
+
requirements:
|
222
|
+
- - ! '>='
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
225
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
226
|
+
none: false
|
227
|
+
requirements:
|
228
|
+
- - ! '>='
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
requirements: []
|
232
|
+
rubyforge_project:
|
233
|
+
rubygems_version: 1.8.23
|
234
|
+
signing_key:
|
235
|
+
specification_version: 3
|
236
|
+
summary: Allows you to strictly validate each of your application's API responses
|
237
|
+
against a declared JSON schema.
|
238
|
+
test_files:
|
239
|
+
- spec/link_header_spec.rb
|
240
|
+
- spec/rack_schema_spec.rb
|
241
|
+
- spec/schemas/auction.json
|
242
|
+
- spec/schemas/object.json
|
243
|
+
- spec/schemas/response.json
|
244
|
+
- spec/schemas/widget.json
|
245
|
+
- spec/spec_helper.rb
|
246
|
+
has_rdoc:
|