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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a9d070ee1c712f33a4d50a9480bea910ff8674ba
4
- data.tar.gz: 34ec8da54e93829dc3123145add042b246f79e46
3
+ metadata.gz: 2e30bacadb0a6825eea17ad9ab5dfbd2fa8ace16
4
+ data.tar.gz: 583d602a06414ccbe915c724c2b35ba961c5dfb1
5
5
  SHA512:
6
- metadata.gz: 507ee6434b83a63a9a75899500f5778a34aa3854b0c68285ceda7bb63f97ad4a66880712f98d15269c3c07aab4bc54745fd0b3e7fcaa1b358da58764ddea43af
7
- data.tar.gz: 0a6076dec2ca43cc4979790b6bfa92e557d119308e2e385f3b3d624079bf7736936b9c77d4f56d12c49c08cdf238c3cc4d3815e66378aee2f9acc2a00fa96312
6
+ metadata.gz: e74b5eb5fe9e52d278aab9b4ce560576e3349d3bca7e4360cfbcdbad5eb4a2f43117f3867f08eb7473d5b7369a316bae7885703650569db5dbe89ff28ffa9673
7
+ data.tar.gz: 6e0c5a3a2b268bec4473d52d9f81682fd21782f5333b3d43c8b61c1b1d62d43084e5b1be460bfe76c88d55e877ced9918ca9a49f5a71bf5f1c99f881844bc8dd
@@ -1,3 +1,7 @@
1
+ ## v0.1.2
2
+ * Change Content-Type validation policy
3
+ * Add Rack::Spec::ResponseValidation
4
+
1
5
  ## v0.1.1
2
6
  * Add ErrorHandler rack middleware for building error response
3
7
 
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # Rack::Spec
2
- Generate API server from [JSON Schema](http://json-schema.org/).
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: JSON.parse(File.read("schema.json")))
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
- * Raise `Rack::Spec::RequestValidation::LinkNotFound` when given request is not defined in schema
27
- * Raise `Rack::Spec::RequestValidation::InvalidContentType` for invalid content type
28
- * Raise `Rack::Spec::RequestValidation::InvalidParameter` for invalid request parameter
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::LinkNotFound
45
- |
46
- |--Rack::Spec::RequestValidation::InvalidContentType
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::RequestValidation::InvalidParameter
65
+ `--Rack::Spec::ResponseValidation::Error
66
+ |
67
+ |--Rack::Spec::ResponseValidation::InvalidResponseContentType
68
+ |
69
+ `--Rack::Spec::ResponseValidation::InvalidResponseType
49
70
  ```
@@ -2,8 +2,10 @@ require "json"
2
2
  require "json_schema"
3
3
  require "multi_json"
4
4
 
5
- require "rack/spec/error_handler"
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
- # Behaves as a rack-middleware
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
- content_type.nil? || Rack::Mime.match?(link.enc_type, content_type)
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 [JsonSchema::Schema::Link, nil] Link for the current action
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
- # path #=> "application/json"
129
- def content_type
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
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Spec
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -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 = "Spec based web-application middleware for Rack."
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"
@@ -9,6 +9,7 @@
9
9
  "type": [
10
10
  "object"
11
11
  ],
12
+ "required": ["id"],
12
13
  "definitions": {
13
14
  "id": {
14
15
  "description": "unique identifier of app",
@@ -1,26 +1,44 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Rack::Spec::RequestValidation do
3
+ describe Rack::Spec do
4
4
  include Rack::Test::Methods
5
5
 
6
6
  let(:app) do
7
- data = schema
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: data
12
+ use Rack::Spec::RequestValidation, schema: local_schema
13
+ use Rack::Spec::ResponseValidation, schema: local_schema
11
14
  run ->(env) do
12
- [200, {}, ["OK"]]
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("../../../fixtures/schema.json", __FILE__)
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 "#call" do
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.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-03 00:00:00.000000000 Z
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/spec/request_validation_spec.rb
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: Spec based web-application middleware for Rack.
188
+ summary: JSON Schema based Rack middlewares
187
189
  test_files:
188
190
  - spec/fixtures/schema.json
189
- - spec/rack/spec/request_validation_spec.rb
191
+ - spec/rack/spec_spec.rb
190
192
  - spec/spec_helper.rb
191
193
  has_rdoc: