esplanade 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -1
- data/.ruby-version +1 -1
- data/.travis.yml +4 -0
- data/README.md +72 -37
- data/esplanade.gemspec +2 -1
- data/lib/esplanade.rb +0 -8
- data/lib/esplanade/configuration.rb +4 -7
- data/lib/esplanade/error.rb +3 -0
- data/lib/esplanade/middleware.rb +8 -21
- data/lib/esplanade/request.rb +16 -18
- data/lib/esplanade/request/doc.rb +43 -0
- data/lib/esplanade/request/raw.rb +23 -0
- data/lib/esplanade/request/raw/body.rb +34 -0
- data/lib/esplanade/request/validation.rb +29 -0
- data/lib/esplanade/response.rb +17 -23
- data/lib/esplanade/response/doc.rb +36 -0
- data/lib/esplanade/response/raw.rb +21 -0
- data/lib/esplanade/response/raw/body.rb +39 -0
- data/lib/esplanade/response/validation.rb +52 -0
- data/lib/esplanade/version.rb +1 -1
- metadata +18 -13
- data/lib/esplanade/railtie.rb +0 -7
- data/lib/esplanade/request/body.rb +0 -14
- data/lib/esplanade/request/error.rb +0 -19
- data/lib/esplanade/response/body.rb +0 -32
- data/lib/esplanade/response/error.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 136faea6ccc9fb08600dfb9c8d597172bb72e270
|
4
|
+
data.tar.gz: 92d86b6dcf3cc3d8d7ad3622bfb99b0c45aabf07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dfbc8ba44102440c149a1d84be28151fe3ed5a918b7d43233ee58a6cb9d7c2713219571f9555d6e5b577184673827b1e7b109299ab5104cb4abefbd997cd754
|
7
|
+
data.tar.gz: ee811221aa1764c746afead9aa85365b035dcc1eecf5f768a27dd503f9acc9c200bccded11c7d7c168a7e482d5aaedbf15ee74ea16273151622565a51b46ae78
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
2
|
+
TargetRubyVersion: 2.2
|
3
3
|
|
4
4
|
Metrics/LineLength:
|
5
5
|
Max: 120
|
@@ -9,3 +9,12 @@ Style/Documentation:
|
|
9
9
|
|
10
10
|
Style/FrozenStringLiteralComment:
|
11
11
|
Enabled: false
|
12
|
+
|
13
|
+
Metrics/BlockLength:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/IfUnlessModifier:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/RescueModifier:
|
20
|
+
Enabled: false
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.2.0
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,21 +1,11 @@
|
|
1
1
|
# Esplanade
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/funbox/esplanade.svg?branch=master)](https://travis-ci.org/funbox/esplanade)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
An example of an error body
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
{
|
13
|
-
"error": [
|
14
|
-
"The property '#/' did not contain a required property of 'login' in schema deee53ed-f917-5f2f-bccf-0b8af3b749c7",
|
15
|
-
"The property '#/' did not contain a required property of 'password' in schema deee53ed-f917-5f2f-bccf-0b8af3b749c7"
|
16
|
-
]
|
17
|
-
}
|
18
|
-
```
|
5
|
+
This gem will help you validation and sinhronize your API in strict accordance to the documentation in
|
6
|
+
[API Blueprint](https://apiblueprint.org/) format.
|
7
|
+
To do this it automatically searches received requestes and responses in the documentation and run validates
|
8
|
+
json-schemas.
|
19
9
|
|
20
10
|
## Installation
|
21
11
|
|
@@ -35,48 +25,93 @@ Or install it yourself as:
|
|
35
25
|
|
36
26
|
## Usage
|
37
27
|
|
38
|
-
|
28
|
+
Example:
|
39
29
|
|
40
|
-
|
30
|
+
`middlewares/your_middleware.rb`
|
41
31
|
|
42
32
|
```ruby
|
43
|
-
|
44
|
-
|
33
|
+
class YourMiddleware < Esplanade::Middleware
|
34
|
+
def call(env)
|
35
|
+
request = Esplanade::Request.new(@documentation, env)
|
36
|
+
request.validation.valid!
|
37
|
+
|
38
|
+
status, headers, body = @app.call(env)
|
39
|
+
|
40
|
+
response = Esplanade::Response.new(request, status, body)
|
41
|
+
response.validation.valid!
|
45
42
|
|
46
|
-
|
43
|
+
[status, headers, body]
|
44
|
+
rescue Esplanade::Error => e
|
45
|
+
your_render_error(e)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
47
49
|
|
48
|
-
config/application.rb
|
50
|
+
`config/application.rb`
|
49
51
|
|
50
52
|
```ruby
|
51
53
|
require 'esplanade'
|
52
|
-
|
54
|
+
Esplanade.configure do |config|
|
55
|
+
config.apib_path = 'doc/backend.apib'
|
56
|
+
end
|
57
|
+
|
58
|
+
require_relative '../middlewares/your_middleware'
|
59
|
+
config.middleware.use YourMiddleware
|
53
60
|
```
|
54
61
|
|
55
|
-
##
|
62
|
+
## Esplanade::Error
|
56
63
|
|
57
|
-
|
64
|
+
From him the `Esplanade::Request::Error` and `Esplanade::Response::Error` are inherited.
|
58
65
|
|
59
|
-
|
66
|
+
### Esplanade::Request::Error
|
60
67
|
|
61
|
-
|
68
|
+
From him the `Esplanade::Request::NotDocumented`, `Esplanade::Request::BodyIsNotJson` and `Esplanade::Request::Invalid` are inherited.
|
62
69
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
70
|
+
#### Esplanade::Request::NotDocumented
|
71
|
+
|
72
|
+
Error message: `{:method=>"method", :path=>"path"}`.
|
73
|
+
|
74
|
+
#### Esplanade::Request::BodyIsNotJson
|
75
|
+
|
76
|
+
Only if the documentation for this request indicates that `Content-Type: application/json`.
|
77
|
+
|
78
|
+
Error message: `{:method=>"method", :path=>"path", :body=>"{\"state\": 1"}`.
|
79
|
+
|
80
|
+
#### Esplanade::Request::Invalid
|
81
|
+
|
82
|
+
Error message: `{:method=>"method", :path=>"path", :body=>"body", :error=>["error"]}`.
|
83
|
+
|
84
|
+
### Esplanade::Response::Error
|
85
|
+
|
86
|
+
From him the `Esplanade::Response::NotDocumented`, `Esplanade::Response::BodyIsNotJson` and `Esplanade::Response::Invalid` are inherited.
|
87
|
+
|
88
|
+
#### Esplanade::Response::NotDocumented
|
89
|
+
|
90
|
+
Error message: `{:request=>{:method=>"method", :path=>"path"}, :status=>"status"}`.
|
91
|
+
|
92
|
+
#### Esplanade::Response::BodyIsNotJson
|
93
|
+
|
94
|
+
Only if the documentation for all the responses of one request indicates that `Content-Type: application/json`.
|
95
|
+
|
96
|
+
Error message: `{:request=>{:method=>"method", :path=>"path"}, :status=>"status", :body=>"body"}`.
|
97
|
+
|
98
|
+
#### Esplanade::Response::Invalid
|
99
|
+
|
100
|
+
Error message: `{:request=>{:method=>"method", :path=>"path"}, :status=>"status", :body=>"body", :error=>["error"]}`.
|
101
|
+
|
102
|
+
## Config
|
68
103
|
|
69
|
-
###
|
104
|
+
### apib_path
|
70
105
|
|
71
|
-
|
106
|
+
Path to API Blueprint documentation. There must be an installed [drafter](https://github.com/apiaryio/drafter) to parse it.
|
72
107
|
|
73
|
-
###
|
108
|
+
### drafter_yaml_path
|
74
109
|
|
75
|
-
|
110
|
+
Path to API Blueprint documentation pre-parsed with `drafter` and saved to a YAML file.
|
76
111
|
|
77
|
-
###
|
112
|
+
### prefix
|
78
113
|
|
79
|
-
|
114
|
+
Prefix of API requests. Example: `'/api'`. The prefix is added to the requests in the documentation.
|
80
115
|
|
81
116
|
## License
|
82
117
|
|
data/esplanade.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'esplanade/version'
|
@@ -19,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
19
20
|
|
20
21
|
spec.add_runtime_dependency 'multi_json', '~> 1.11', '>= 1.11.1'
|
21
22
|
spec.add_runtime_dependency 'json-schema', '~> 2.6', '>= 2.6.2'
|
22
|
-
spec.add_runtime_dependency '
|
23
|
+
spec.add_runtime_dependency 'tomograph', '~> 1.1', '>= 1.1.0'
|
23
24
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
24
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
25
26
|
spec.add_development_dependency 'byebug', '~> 8.2', '>= 8.2.1'
|
data/lib/esplanade.rb
CHANGED
@@ -1,13 +1,5 @@
|
|
1
1
|
require 'esplanade/middleware'
|
2
2
|
require 'esplanade/configuration'
|
3
|
-
require 'esplanade/request'
|
4
|
-
require 'esplanade/request/body'
|
5
|
-
require 'esplanade/request/error'
|
6
|
-
require 'esplanade/response'
|
7
|
-
require 'esplanade/response/body'
|
8
|
-
require 'esplanade/response/error'
|
9
|
-
require 'esplanade/railtie' if defined?(Rails)
|
10
|
-
require 'tomogram_routing'
|
11
3
|
|
12
4
|
module Esplanade
|
13
5
|
class << self
|
@@ -1,14 +1,11 @@
|
|
1
1
|
module Esplanade
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :
|
4
|
-
:
|
5
|
-
:
|
6
|
-
:validation_response
|
3
|
+
attr_accessor :apib_path,
|
4
|
+
:drafter_yaml_path,
|
5
|
+
:prefix
|
7
6
|
|
8
7
|
def initialize
|
9
|
-
@
|
10
|
-
@validation_requests = true
|
11
|
-
@validation_response = true
|
8
|
+
@prefix = ''
|
12
9
|
end
|
13
10
|
end
|
14
11
|
end
|
data/lib/esplanade/middleware.rb
CHANGED
@@ -1,33 +1,20 @@
|
|
1
|
-
require '
|
1
|
+
require 'tomograph'
|
2
|
+
require 'esplanade/request'
|
3
|
+
require 'esplanade/response'
|
2
4
|
|
3
5
|
module Esplanade
|
4
6
|
class Middleware
|
5
|
-
class Exit < RuntimeError; end
|
6
|
-
class ResponseNotDoc < RuntimeError; end
|
7
|
-
|
8
7
|
def initialize(app)
|
9
8
|
@app = app
|
10
|
-
@
|
9
|
+
@documentation = Tomograph::Tomogram.new(
|
10
|
+
prefix: Esplanade.configuration.prefix,
|
11
|
+
apib_path: Esplanade.configuration.apib_path,
|
12
|
+
drafter_yaml_path: Esplanade.configuration.drafter_yaml_path
|
13
|
+
)
|
11
14
|
end
|
12
15
|
|
13
16
|
def call(env)
|
14
|
-
valid!(env)
|
15
|
-
rescue Request::NotDocumented
|
16
|
-
Request::Error.not_documented
|
17
|
-
rescue Response::NotDocumented
|
18
|
-
Response::Error.not_documented
|
19
|
-
rescue Request::Unsuitable => e
|
20
|
-
Request::Error.unsuitable(e.message)
|
21
|
-
rescue Response::Unsuitable => e
|
22
|
-
Response::Error.unsuitable(e.message)
|
23
|
-
end
|
24
|
-
|
25
|
-
def valid!(env)
|
26
|
-
request = Request.new(env, @tomogram)
|
27
|
-
request.valid! if request.validate?
|
28
17
|
status, headers, body = @app.call(env)
|
29
|
-
response = Response.new(status, body, request.schema)
|
30
|
-
response.valid! if response.validate?
|
31
18
|
[status, headers, body]
|
32
19
|
end
|
33
20
|
end
|
data/lib/esplanade/request.rb
CHANGED
@@ -1,31 +1,29 @@
|
|
1
|
-
require '
|
1
|
+
require 'esplanade/request/doc'
|
2
|
+
require 'esplanade/request/raw'
|
3
|
+
require 'esplanade/request/validation'
|
2
4
|
|
3
5
|
module Esplanade
|
4
6
|
class Request
|
5
|
-
|
7
|
+
class Error < Esplanade::Error; end
|
8
|
+
class NotDocumented < Error; end
|
9
|
+
class BodyIsNotJson < Error; end
|
10
|
+
class Invalid < Error; end
|
6
11
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(env, tomogram)
|
11
|
-
@method = env['REQUEST_METHOD']
|
12
|
-
@path = env['PATH_INFO']
|
13
|
-
@body = Body.craft(env)
|
14
|
-
@schema = tomogram.find_request(method: @method, path: @path)
|
15
|
-
raise NotDocumented unless schema || Esplanade.configuration.skip_not_documented
|
16
|
-
self
|
12
|
+
def initialize(documentation, env)
|
13
|
+
@documentation = documentation
|
14
|
+
@env = env
|
17
15
|
end
|
18
16
|
|
19
|
-
def
|
20
|
-
|
17
|
+
def doc
|
18
|
+
@doc ||= Doc.new(@documentation, raw)
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
24
|
-
|
21
|
+
def raw
|
22
|
+
@raw ||= Raw.new(@env)
|
25
23
|
end
|
26
24
|
|
27
|
-
def
|
28
|
-
@
|
25
|
+
def validation
|
26
|
+
@validation || Validation.new(doc, raw)
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Esplanade
|
2
|
+
class Request
|
3
|
+
class Doc
|
4
|
+
def initialize(main_documentation, raw)
|
5
|
+
@main_documentation = main_documentation
|
6
|
+
@raw = raw
|
7
|
+
end
|
8
|
+
|
9
|
+
def tomogram
|
10
|
+
@tomogram ||= @main_documentation.find_request(method: @raw.method, path: @raw.path)
|
11
|
+
raise NotDocumented, message if @tomogram.nil?
|
12
|
+
@tomogram
|
13
|
+
end
|
14
|
+
|
15
|
+
def json_schema
|
16
|
+
@json_schema ||= tomogram.request
|
17
|
+
end
|
18
|
+
|
19
|
+
def method
|
20
|
+
@method ||= tomogram.method
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@path ||= tomogram.path.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def responses
|
28
|
+
@responses ||= tomogram.responses
|
29
|
+
rescue NotDocumented
|
30
|
+
[]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def message
|
36
|
+
{
|
37
|
+
method: @raw.method,
|
38
|
+
path: @raw.path
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'esplanade/request/raw/body'
|
2
|
+
|
3
|
+
module Esplanade
|
4
|
+
class Request
|
5
|
+
class Raw
|
6
|
+
def initialize(env)
|
7
|
+
@env = env
|
8
|
+
end
|
9
|
+
|
10
|
+
def method
|
11
|
+
@method ||= @env['REQUEST_METHOD']
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
@path ||= @env['PATH_INFO']
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
@body ||= Body.new(self, @env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Esplanade
|
4
|
+
class Request
|
5
|
+
class Raw
|
6
|
+
class Body
|
7
|
+
def initialize(raw_request, env)
|
8
|
+
@raw_request = raw_request
|
9
|
+
@env = env
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_string
|
13
|
+
@string ||= @env['rack.request.form_vars']
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
@hash ||= MultiJson.load(to_string)
|
18
|
+
rescue MultiJson::ParseError
|
19
|
+
raise BodyIsNotJson, message
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def message
|
25
|
+
{
|
26
|
+
method: @raw_request.method,
|
27
|
+
path: @raw_request.path,
|
28
|
+
body: to_string
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'json-schema'
|
2
|
+
require 'esplanade/error'
|
3
|
+
|
4
|
+
module Esplanade
|
5
|
+
class Request
|
6
|
+
class Validation
|
7
|
+
def initialize(doc, raw)
|
8
|
+
@doc = doc
|
9
|
+
@raw = raw
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid!
|
13
|
+
@error ||= JSON::Validator.fully_validate(@doc.json_schema, @raw.body.to_hash)
|
14
|
+
raise Invalid, message if @error != []
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def message
|
20
|
+
{
|
21
|
+
method: @raw.method,
|
22
|
+
path: @raw.path,
|
23
|
+
body: @raw.body.to_string,
|
24
|
+
error: @error
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/esplanade/response.rb
CHANGED
@@ -1,38 +1,32 @@
|
|
1
|
-
require '
|
1
|
+
require 'esplanade/response/doc'
|
2
|
+
require 'esplanade/response/raw'
|
3
|
+
require 'esplanade/response/validation'
|
2
4
|
|
3
5
|
module Esplanade
|
4
6
|
class Response
|
5
|
-
|
7
|
+
class Error < Esplanade::Error; end
|
8
|
+
class NotDocumented < Error; end
|
9
|
+
class BodyIsNotJson < Error; end
|
10
|
+
class Invalid < Error; end
|
6
11
|
|
7
|
-
|
8
|
-
class NotDocumented < RuntimeError; end
|
12
|
+
attr_reader :request
|
9
13
|
|
10
|
-
def initialize(
|
11
|
-
|
14
|
+
def initialize(request, status, raw_body)
|
15
|
+
@request = request
|
12
16
|
@status = status
|
13
|
-
@
|
14
|
-
@schemas = expect_request.find_responses(status: @status)
|
15
|
-
raise NotDocumented unless (@schemas&.first) || Esplanade.configuration.skip_not_documented
|
16
|
-
self
|
17
|
+
@raw_body = raw_body
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
@schemas.each do |action|
|
23
|
-
res = JSON::Validator.fully_validate(action['body'], @body)
|
24
|
-
return res if res == []
|
25
|
-
end
|
26
|
-
|
27
|
-
['invalid']
|
20
|
+
def doc
|
21
|
+
@doc ||= Doc.new(@request, raw)
|
28
22
|
end
|
29
23
|
|
30
|
-
def
|
31
|
-
|
24
|
+
def raw
|
25
|
+
@raw ||= Raw.new(@request, @status, @raw_body)
|
32
26
|
end
|
33
27
|
|
34
|
-
def
|
35
|
-
@
|
28
|
+
def validation
|
29
|
+
@validation ||= Validation.new(@request, doc, raw)
|
36
30
|
end
|
37
31
|
end
|
38
32
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Esplanade
|
2
|
+
class Response
|
3
|
+
class Doc
|
4
|
+
def initialize(request, raw)
|
5
|
+
@request = request
|
6
|
+
@raw = raw
|
7
|
+
end
|
8
|
+
|
9
|
+
def tomogram
|
10
|
+
@tomogram ||= @request.doc.responses.find_all { |response| response['status'] == @raw.status }
|
11
|
+
raise NotDocumented, message if @tomogram == []
|
12
|
+
@tomogram
|
13
|
+
end
|
14
|
+
|
15
|
+
def json_schemas
|
16
|
+
@json_schemas ||= tomogram.map { |action| action['body'] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
@status ||= tomogram['status']
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def message
|
26
|
+
{
|
27
|
+
request: {
|
28
|
+
method: @request.raw.method,
|
29
|
+
path: @request.raw.path
|
30
|
+
},
|
31
|
+
status: @raw.status
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'esplanade/response/raw/body'
|
2
|
+
|
3
|
+
module Esplanade
|
4
|
+
class Response
|
5
|
+
class Raw
|
6
|
+
def initialize(request, raw_status, raw_body)
|
7
|
+
@request = request
|
8
|
+
@raw_status = raw_status
|
9
|
+
@raw_body = raw_body
|
10
|
+
end
|
11
|
+
|
12
|
+
def status
|
13
|
+
@status ||= @raw_status.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def body
|
17
|
+
@body ||= Body.new(@request, self, @raw_body)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Esplanade
|
4
|
+
class Response
|
5
|
+
class Raw
|
6
|
+
class Body
|
7
|
+
def initialize(request, raw_response, raw_body)
|
8
|
+
@request = request
|
9
|
+
@raw_response = raw_response
|
10
|
+
@raw_body = raw_body
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_string
|
14
|
+
@string ||= @raw_body.body rescue nil
|
15
|
+
@string ||= @raw_body.first rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
@hash ||= MultiJson.load(to_string)
|
20
|
+
rescue MultiJson::ParseError
|
21
|
+
raise BodyIsNotJson, message
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def message
|
27
|
+
{
|
28
|
+
request: {
|
29
|
+
method: @request.raw.method,
|
30
|
+
path: @request.raw.path
|
31
|
+
},
|
32
|
+
status: @raw_response.status,
|
33
|
+
body: @raw_response.body.to_string
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json-schema'
|
2
|
+
|
3
|
+
module Esplanade
|
4
|
+
class Response
|
5
|
+
class Validation
|
6
|
+
def initialize(request, doc, raw)
|
7
|
+
@request = request
|
8
|
+
@doc = doc
|
9
|
+
@raw = raw
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid!
|
13
|
+
@error ||= if @doc.json_schemas.size == 1
|
14
|
+
one_json_schema
|
15
|
+
else
|
16
|
+
more_than_one_json_schema
|
17
|
+
end
|
18
|
+
raise Invalid, message if @error != []
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def one_json_schema
|
24
|
+
JSON::Validator.fully_validate(@doc.json_schemas.first, @raw.body.to_hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def more_than_one_json_schema
|
28
|
+
main_res = @doc.json_schemas.each do |json_schema|
|
29
|
+
res = JSON::Validator.fully_validate(json_schema, @raw.body.to_hash)
|
30
|
+
break res if res == []
|
31
|
+
end
|
32
|
+
if main_res != []
|
33
|
+
['invalid']
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def message
|
40
|
+
{
|
41
|
+
request: {
|
42
|
+
method: @request.raw.method,
|
43
|
+
path: @request.raw.path
|
44
|
+
},
|
45
|
+
status: @raw.status,
|
46
|
+
body: @raw.body.to_string,
|
47
|
+
error: @error
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/esplanade/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: esplanade
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- d.efimov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -51,25 +51,25 @@ dependencies:
|
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: 2.6.2
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
|
-
name:
|
54
|
+
name: tomograph
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - "~>"
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '
|
59
|
+
version: '1.1'
|
60
60
|
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
62
|
+
version: 1.1.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
69
|
+
version: '1.1'
|
70
70
|
- - ">="
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
72
|
+
version: 1.1.0
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: bundler
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -188,6 +188,7 @@ files:
|
|
188
188
|
- ".gitignore"
|
189
189
|
- ".rubocop.yml"
|
190
190
|
- ".ruby-version"
|
191
|
+
- ".travis.yml"
|
191
192
|
- CODE_OF_CONDUCT.md
|
192
193
|
- Gemfile
|
193
194
|
- LICENSE.txt
|
@@ -198,14 +199,18 @@ files:
|
|
198
199
|
- esplanade.gemspec
|
199
200
|
- lib/esplanade.rb
|
200
201
|
- lib/esplanade/configuration.rb
|
202
|
+
- lib/esplanade/error.rb
|
201
203
|
- lib/esplanade/middleware.rb
|
202
|
-
- lib/esplanade/railtie.rb
|
203
204
|
- lib/esplanade/request.rb
|
204
|
-
- lib/esplanade/request/
|
205
|
-
- lib/esplanade/request/
|
205
|
+
- lib/esplanade/request/doc.rb
|
206
|
+
- lib/esplanade/request/raw.rb
|
207
|
+
- lib/esplanade/request/raw/body.rb
|
208
|
+
- lib/esplanade/request/validation.rb
|
206
209
|
- lib/esplanade/response.rb
|
207
|
-
- lib/esplanade/response/
|
208
|
-
- lib/esplanade/response/
|
210
|
+
- lib/esplanade/response/doc.rb
|
211
|
+
- lib/esplanade/response/raw.rb
|
212
|
+
- lib/esplanade/response/raw/body.rb
|
213
|
+
- lib/esplanade/response/validation.rb
|
209
214
|
- lib/esplanade/version.rb
|
210
215
|
homepage:
|
211
216
|
licenses:
|
@@ -227,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
232
|
version: '0'
|
228
233
|
requirements: []
|
229
234
|
rubyforge_project:
|
230
|
-
rubygems_version: 2.5
|
235
|
+
rubygems_version: 2.4.5
|
231
236
|
signing_key:
|
232
237
|
specification_version: 4
|
233
238
|
summary: Validate requests and responses against API Blueprint specifications
|
data/lib/esplanade/railtie.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module Esplanade
|
2
|
-
class Request
|
3
|
-
class Body < Hash
|
4
|
-
private_class_method :new
|
5
|
-
|
6
|
-
def self.craft(env)
|
7
|
-
params_string = env['rack.input'].read
|
8
|
-
params_hash = {}
|
9
|
-
params_hash = MultiJson.load(params_string) unless params_string == ''
|
10
|
-
new.merge(params_hash)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Esplanade
|
2
|
-
class Request
|
3
|
-
class Error
|
4
|
-
def self.unsuitable(message)
|
5
|
-
status = '400'
|
6
|
-
headers = { 'Content-Type' => 'application/json; charset=utf-8' }
|
7
|
-
body = [MultiJson.dump(error: [message[2..-3]])]
|
8
|
-
[status, headers, body]
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.not_documented
|
12
|
-
status = '400'
|
13
|
-
headers = { 'Content-Type' => 'application/json; charset=utf-8' }
|
14
|
-
body = [MultiJson.dump(error: ['Not documented'])]
|
15
|
-
[status, headers, body]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module Esplanade
|
2
|
-
class Response
|
3
|
-
class Body < Hash
|
4
|
-
class << self
|
5
|
-
private_class_method :new
|
6
|
-
|
7
|
-
def craft(body)
|
8
|
-
# According to specification Rack http://rack.github.io
|
9
|
-
# body can only answer each
|
10
|
-
lines = []
|
11
|
-
body.each { |line| lines.push(line) }
|
12
|
-
lines_to_json(lines)
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def lines_to_json(lines)
|
18
|
-
if lines.join.empty?
|
19
|
-
{}
|
20
|
-
else
|
21
|
-
res = lines.join('\n')
|
22
|
-
begin
|
23
|
-
MultiJson.load(res)
|
24
|
-
rescue MultiJson::ParseError
|
25
|
-
res
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Esplanade
|
2
|
-
class Response
|
3
|
-
class Error
|
4
|
-
def self.unsuitable(message)
|
5
|
-
status = '500'
|
6
|
-
headers = { 'Content-Type' => 'application/json; charset=utf-8' }
|
7
|
-
body = [MultiJson.dump(error: [message[2..-3]])]
|
8
|
-
[status, headers, body]
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.not_documented
|
12
|
-
status = '500'
|
13
|
-
headers = { 'Content-Type' => 'application/json; charset=utf-8' }
|
14
|
-
body = [MultiJson.dump(error: ['Not documented'])]
|
15
|
-
[status, headers, body]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|