rack-spec 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +32 -11
- data/lib/rack/spec.rb +3 -1
- data/lib/rack/spec/base_validator.rb +53 -0
- data/lib/rack/spec/request_validation.rb +7 -41
- data/lib/rack/spec/response_validation.rb +114 -0
- data/lib/rack/spec/version.rb +1 -1
- data/rack-spec.gemspec +1 -1
- data/spec/fixtures/schema.json +1 -0
- data/spec/rack/{spec/request_validation_spec.rb → spec_spec.rb} +89 -6
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e30bacadb0a6825eea17ad9ab5dfbd2fa8ace16
|
4
|
+
data.tar.gz: 583d602a06414ccbe915c724c2b35ba961c5dfb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e74b5eb5fe9e52d278aab9b4ce560576e3349d3bca7e4360cfbcdbad5eb4a2f43117f3867f08eb7473d5b7369a316bae7885703650569db5dbe89ff28ffa9673
|
7
|
+
data.tar.gz: 6e0c5a3a2b268bec4473d52d9f81682fd21782f5333b3d43c8b61c1b1d62d43084e5b1be460bfe76c88d55e877ced9918ca9a49f5a71bf5f1c99f881844bc8dd
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# Rack::Spec
|
2
|
-
|
2
|
+
[JSON Schema](http://json-schema.org/) based Rack middlewares.
|
3
3
|
|
4
4
|
## Usage
|
5
5
|
```ruby
|
6
|
+
str = File.read("schema.json")
|
7
|
+
schema = JSON.parse(str)
|
8
|
+
|
6
9
|
use Rack::Spec::ErrorHandler
|
7
|
-
use Rack::Spec::RequestValidation, schema:
|
10
|
+
use Rack::Spec::RequestValidation, schema: schema
|
11
|
+
use Rack::Spec::ResponseValidation, schema: schema if ENV["RACK_ENV"] == "test"
|
8
12
|
```
|
9
13
|
|
10
14
|
### Example
|
@@ -23,9 +27,18 @@ $ curl http://localhost:9292/apps -H "Content-Type: application/json" -d '{"name
|
|
23
27
|
```
|
24
28
|
|
25
29
|
### Rack::Spec::RequestValidation
|
26
|
-
|
27
|
-
|
28
|
-
*
|
30
|
+
Validates request and raises following errors:
|
31
|
+
|
32
|
+
* Rack::Spec::RequestValidation::InvalidContentType
|
33
|
+
* Rack::Spec::RequestValidation::InvalidJson
|
34
|
+
* Rack::Spec::RequestValidation::InvalidParameter
|
35
|
+
* Rack::Spec::RequestValidation::LinkNotFound
|
36
|
+
|
37
|
+
### Rack::Spec::RequestValidation
|
38
|
+
Validates response and raises following errors:
|
39
|
+
|
40
|
+
* Rack::Spec::RequestValidation::InvalidResponseContentType
|
41
|
+
* Rack::Spec::RequestValidation::InvalidResponseType
|
29
42
|
|
30
43
|
### Rack::Spec::ErrorHandler
|
31
44
|
Returns appropriate error response including following properties when RequestValidation raises error.
|
@@ -39,11 +52,19 @@ StandardError
|
|
39
52
|
|
|
40
53
|
Rack::Spec::Error
|
41
54
|
|
|
42
|
-
Rack::Spec::RequestValidation::Error
|
43
|
-
|
|
44
|
-
|--Rack::Spec::RequestValidation::
|
45
|
-
|
|
46
|
-
|--Rack::Spec::RequestValidation::
|
55
|
+
|--Rack::Spec::RequestValidation::Error
|
56
|
+
| |
|
57
|
+
| |--Rack::Spec::RequestValidation::InvalidContentType
|
58
|
+
| |
|
59
|
+
| |--Rack::Spec::RequestValidation::InvalidJson
|
60
|
+
| |
|
61
|
+
| |--Rack::Spec::RequestValidation::InvalidParameter
|
62
|
+
| |
|
63
|
+
| `--Rack::Spec::RequestValidation::LinkNotFound
|
47
64
|
|
|
48
|
-
`--Rack::Spec::
|
65
|
+
`--Rack::Spec::ResponseValidation::Error
|
66
|
+
|
|
67
|
+
|--Rack::Spec::ResponseValidation::InvalidResponseContentType
|
68
|
+
|
|
69
|
+
`--Rack::Spec::ResponseValidation::InvalidResponseType
|
49
70
|
```
|
data/lib/rack/spec.rb
CHANGED
@@ -2,8 +2,10 @@ require "json"
|
|
2
2
|
require "json_schema"
|
3
3
|
require "multi_json"
|
4
4
|
|
5
|
-
require "rack/spec/
|
5
|
+
require "rack/spec/base_validator"
|
6
6
|
require "rack/spec/error"
|
7
|
+
require "rack/spec/error_handler"
|
7
8
|
require "rack/spec/request_validation"
|
9
|
+
require "rack/spec/response_validation"
|
8
10
|
require "rack/spec/schema"
|
9
11
|
require "rack/spec/version"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rack
|
2
|
+
module Spec
|
3
|
+
class BaseValidator
|
4
|
+
# Utility wrapper method
|
5
|
+
def self.call(**args)
|
6
|
+
new(**args).call
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param env [Hash] Rack env
|
10
|
+
# @param schema [JsonSchema::Schema] Schema object
|
11
|
+
def initialize(env: nil, schema: nil)
|
12
|
+
@env = env
|
13
|
+
@schema = schema
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Treats env as a utility object to easily extract method and path
|
19
|
+
# @return [Rack::Request]
|
20
|
+
def request
|
21
|
+
@request ||= Rack::Request.new(@env)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] HTTP request method
|
25
|
+
# @example
|
26
|
+
# method #=> "GET"
|
27
|
+
def method
|
28
|
+
request.request_method
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] Request path
|
32
|
+
# @example
|
33
|
+
# path #=> "/recipes"
|
34
|
+
def path
|
35
|
+
request.path_info
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [JsonSchema::Schema::Link, nil] Link for the current action
|
39
|
+
def link
|
40
|
+
if instance_variable_defined?(:@link)
|
41
|
+
@link
|
42
|
+
else
|
43
|
+
@link = @schema.link_for(method: method, path: path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [true, false] True if link is defined for the current action
|
48
|
+
def has_link_for_current_action?
|
49
|
+
!!link
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -10,14 +10,14 @@ module Rack
|
|
10
10
|
@schema = Schema.new(schema)
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
13
|
+
# @raise [Rack::Spec::RequestValidation::Error] Raises if given request is invalid to JSON Schema
|
14
14
|
# @param env [Hash] Rack env
|
15
15
|
def call(env)
|
16
16
|
Validator.call(env: env, schema: @schema)
|
17
17
|
@app.call(env)
|
18
18
|
end
|
19
19
|
|
20
|
-
class Validator
|
20
|
+
class Validator < BaseValidator
|
21
21
|
# Utility wrapper method
|
22
22
|
def self.call(**args)
|
23
23
|
new(**args).call
|
@@ -71,12 +71,7 @@ module Rack
|
|
71
71
|
|
72
72
|
# @return [true, false] True if no or matched content type given
|
73
73
|
def has_valid_content_type?
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
# @return [true, false] True if link is defined for the current action
|
78
|
-
def has_link_for_current_action?
|
79
|
-
!!link
|
74
|
+
mime_type.nil? || Rack::Mime.match?(link.enc_type, mime_type)
|
80
75
|
end
|
81
76
|
|
82
77
|
# @return [Array] A result of schema validation for the current action
|
@@ -94,40 +89,11 @@ module Rack
|
|
94
89
|
JsonSchema::SchemaError.aggregate(schema_validation_errors).join("\n")
|
95
90
|
end
|
96
91
|
|
97
|
-
# @return [
|
98
|
-
def link
|
99
|
-
if instance_variable_defined?(:@link)
|
100
|
-
@link
|
101
|
-
else
|
102
|
-
@link = @schema.link_for(method: method, path: path)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# Treats env as a utility object to easily extract method and path
|
107
|
-
# @return [Rack::Request]
|
108
|
-
def request
|
109
|
-
@request ||= Rack::Request.new(@env)
|
110
|
-
end
|
111
|
-
|
112
|
-
# @return [String] HTTP request method
|
113
|
-
# @example
|
114
|
-
# method #=> "GET"
|
115
|
-
def method
|
116
|
-
request.request_method
|
117
|
-
end
|
118
|
-
|
119
|
-
# @return [String] Request path
|
120
|
-
# @example
|
121
|
-
# path #=> "/recipes"
|
122
|
-
def path
|
123
|
-
request.path_info
|
124
|
-
end
|
125
|
-
|
126
|
-
# @return [String] Request content type
|
92
|
+
# @return [String, nil] Request MIME Type specified in Content-Type header field
|
127
93
|
# @example
|
128
|
-
#
|
129
|
-
def
|
130
|
-
request.content_type
|
94
|
+
# mime_type #=> "application/json"
|
95
|
+
def mime_type
|
96
|
+
request.content_type.split(";").first if request.content_type
|
131
97
|
end
|
132
98
|
|
133
99
|
# @return [String] request body
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Rack
|
2
|
+
module Spec
|
3
|
+
class ResponseValidation
|
4
|
+
# Behaves as a rack-middleware
|
5
|
+
# @param app [Object] Rack application
|
6
|
+
# @param schema [Hash] Schema object written in JSON schema format
|
7
|
+
# @raise [JsonSchema::SchemaError]
|
8
|
+
def initialize(app, schema: nil)
|
9
|
+
@app = app
|
10
|
+
@schema = Schema.new(schema)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @raise [Rack::Spec::ResponseValidation::Error]
|
14
|
+
# @param env [Hash] Rack env
|
15
|
+
def call(env)
|
16
|
+
@app.call(env).tap do |response|
|
17
|
+
Validator.call(env: env, response: response, schema: @schema)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Validator < BaseValidator
|
22
|
+
# @param env [Hash] Rack env
|
23
|
+
# @param response [Array] Rack response
|
24
|
+
# @param schema [JsonSchema::Schema] Schema object
|
25
|
+
def initialize(env: nil, response: nil, schema: nil)
|
26
|
+
@env = env
|
27
|
+
@response = response
|
28
|
+
@schema = schema
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raises an error if any error detected
|
32
|
+
# @raise [Rack::Spec::ResponseValidation::InvalidResponse]
|
33
|
+
def call
|
34
|
+
case
|
35
|
+
when !has_json_content_type?
|
36
|
+
raise InvalidResponseContentType
|
37
|
+
when !valid?
|
38
|
+
raise InvalidResponseType, validator.errors
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [true, false] True if response Content-Type is for JSON
|
43
|
+
def has_json_content_type?
|
44
|
+
%r<\Aapplication/.*json> === headers["Content-Type"]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [true, false] True if given data is valid to the JSON schema
|
48
|
+
def valid?
|
49
|
+
!has_link_for_current_action? || validator.validate(data)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Array, Hash] Response body data, decoded from JSON
|
53
|
+
def data
|
54
|
+
MultiJson.decode(body)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [JsonSchema::Validator]
|
58
|
+
# @note The result is memoized for returning errors in invalid case
|
59
|
+
def validator
|
60
|
+
@validator ||= JsonSchema::Validator.new(schema_for_current_link)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [JsonSchema::Schema] Schema for current link, specified by targetSchema or parent schema
|
64
|
+
def schema_for_current_link
|
65
|
+
link.target_schema || link.parent
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Hash] Response headers
|
69
|
+
def headers
|
70
|
+
@response[1]
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] Response body
|
74
|
+
def body
|
75
|
+
@response[2].reduce("") do |result, str|
|
76
|
+
result << str
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Base error class for Rack::Spec::ResponseValidation
|
82
|
+
class Error < Error
|
83
|
+
end
|
84
|
+
|
85
|
+
class InvalidResponseType < Error
|
86
|
+
def initialize(errors)
|
87
|
+
super JsonSchema::SchemaError.aggregate(errors).join(" ")
|
88
|
+
end
|
89
|
+
|
90
|
+
def id
|
91
|
+
"invalid_response_type"
|
92
|
+
end
|
93
|
+
|
94
|
+
def status
|
95
|
+
500
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class InvalidResponseContentType < Error
|
100
|
+
def initialize
|
101
|
+
super("Response Content-Type wasn't for JSON")
|
102
|
+
end
|
103
|
+
|
104
|
+
def id
|
105
|
+
"invalid_response_content_type"
|
106
|
+
end
|
107
|
+
|
108
|
+
def status
|
109
|
+
500
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/rack/spec/version.rb
CHANGED
data/rack-spec.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Rack::Spec::VERSION
|
8
8
|
spec.authors = ["Ryo Nakamura"]
|
9
9
|
spec.email = ["r7kamura@gmail.com"]
|
10
|
-
spec.summary = "
|
10
|
+
spec.summary = "JSON Schema based Rack middlewares"
|
11
11
|
|
12
12
|
spec.homepage = "https://github.com/r7kamura/rack-spec"
|
13
13
|
spec.license = "MIT"
|
data/spec/fixtures/schema.json
CHANGED
@@ -1,26 +1,44 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Rack::Spec
|
3
|
+
describe Rack::Spec do
|
4
4
|
include Rack::Test::Methods
|
5
5
|
|
6
6
|
let(:app) do
|
7
|
-
|
7
|
+
local_schema = schema
|
8
|
+
local_response_body = response_body
|
9
|
+
local_response_headers = response_headers
|
8
10
|
Rack::Builder.app do
|
9
11
|
use Rack::Spec::ErrorHandler
|
10
|
-
use Rack::Spec::RequestValidation, schema:
|
12
|
+
use Rack::Spec::RequestValidation, schema: local_schema
|
13
|
+
use Rack::Spec::ResponseValidation, schema: local_schema
|
11
14
|
run ->(env) do
|
12
|
-
[200,
|
15
|
+
[200, local_response_headers, [local_response_body]]
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
20
|
+
let(:response_headers) do
|
21
|
+
{ "Content-Type" => "application/json" }
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:response_body) do
|
25
|
+
response_data.to_json
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:response_data) do
|
29
|
+
{
|
30
|
+
id: "01234567-89ab-cdef-0123-456789abcdef",
|
31
|
+
name: "example",
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
17
35
|
let(:schema) do
|
18
36
|
str = File.read(schema_path)
|
19
37
|
JSON.parse(str)
|
20
38
|
end
|
21
39
|
|
22
40
|
let(:schema_path) do
|
23
|
-
File.expand_path("
|
41
|
+
File.expand_path("../../fixtures/schema.json", __FILE__)
|
24
42
|
end
|
25
43
|
|
26
44
|
let(:response) do
|
@@ -58,7 +76,7 @@ describe Rack::Spec::RequestValidation do
|
|
58
76
|
end
|
59
77
|
end
|
60
78
|
|
61
|
-
describe "
|
79
|
+
describe "RequestValidation" do
|
62
80
|
let(:verb) do
|
63
81
|
:get
|
64
82
|
end
|
@@ -103,6 +121,13 @@ describe Rack::Spec::RequestValidation do
|
|
103
121
|
it { should == 200 }
|
104
122
|
end
|
105
123
|
|
124
|
+
context "with suffixed content type", :with_valid_post_request do
|
125
|
+
before do
|
126
|
+
env["CONTENT_TYPE"] = "application/json; charset=utf8"
|
127
|
+
end
|
128
|
+
it { should == 200 }
|
129
|
+
end
|
130
|
+
|
106
131
|
context "with invalid request property", :with_valid_post_request do
|
107
132
|
let(:params) do
|
108
133
|
{ name: "ab" }.to_json
|
@@ -131,4 +156,62 @@ describe Rack::Spec::RequestValidation do
|
|
131
156
|
end
|
132
157
|
end
|
133
158
|
end
|
159
|
+
|
160
|
+
describe "ResponseValidation" do
|
161
|
+
let(:verb) do
|
162
|
+
:get
|
163
|
+
end
|
164
|
+
|
165
|
+
let(:path) do
|
166
|
+
"/apps"
|
167
|
+
end
|
168
|
+
|
169
|
+
let(:body) do
|
170
|
+
{
|
171
|
+
foo: "bar",
|
172
|
+
}.to_json
|
173
|
+
end
|
174
|
+
|
175
|
+
context "with response content type except for JSON" do
|
176
|
+
let(:response_headers) do
|
177
|
+
{ "Content-Type" => "text/plain" }
|
178
|
+
end
|
179
|
+
|
180
|
+
it "returns invalid_response_content_type error" do
|
181
|
+
should == 500
|
182
|
+
response.body.should be_json_as(
|
183
|
+
id: "invalid_response_content_type",
|
184
|
+
message: "Response Content-Type wasn't for JSON",
|
185
|
+
)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "without required field" do
|
190
|
+
before do
|
191
|
+
response_data.delete(:id)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "returns invalid_response_type error" do
|
195
|
+
should == 500
|
196
|
+
response.body.should be_json_as(
|
197
|
+
id: "invalid_response_type",
|
198
|
+
message: /Missing required keys "id" in object/,
|
199
|
+
)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "with invalid pattern string field" do
|
204
|
+
before do
|
205
|
+
response_data[:id] = "123"
|
206
|
+
end
|
207
|
+
|
208
|
+
it "returns invalid_response_type error" do
|
209
|
+
should == 500
|
210
|
+
response.body.should be_json_as(
|
211
|
+
id: "invalid_response_type",
|
212
|
+
message: /Expected data to match "uuid" format, value was: 123/,
|
213
|
+
)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
134
217
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-spec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryo Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json_schema
|
@@ -151,14 +151,16 @@ files:
|
|
151
151
|
- Rakefile
|
152
152
|
- lib/rack-spec.rb
|
153
153
|
- lib/rack/spec.rb
|
154
|
+
- lib/rack/spec/base_validator.rb
|
154
155
|
- lib/rack/spec/error.rb
|
155
156
|
- lib/rack/spec/error_handler.rb
|
156
157
|
- lib/rack/spec/request_validation.rb
|
158
|
+
- lib/rack/spec/response_validation.rb
|
157
159
|
- lib/rack/spec/schema.rb
|
158
160
|
- lib/rack/spec/version.rb
|
159
161
|
- rack-spec.gemspec
|
160
162
|
- spec/fixtures/schema.json
|
161
|
-
- spec/rack/
|
163
|
+
- spec/rack/spec_spec.rb
|
162
164
|
- spec/spec_helper.rb
|
163
165
|
homepage: https://github.com/r7kamura/rack-spec
|
164
166
|
licenses:
|
@@ -183,9 +185,9 @@ rubyforge_project:
|
|
183
185
|
rubygems_version: 2.2.2
|
184
186
|
signing_key:
|
185
187
|
specification_version: 4
|
186
|
-
summary:
|
188
|
+
summary: JSON Schema based Rack middlewares
|
187
189
|
test_files:
|
188
190
|
- spec/fixtures/schema.json
|
189
|
-
- spec/rack/
|
191
|
+
- spec/rack/spec_spec.rb
|
190
192
|
- spec/spec_helper.rb
|
191
193
|
has_rdoc:
|