rack-spec 0.1.1 → 0.1.2
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.
- 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:
|