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 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: