swaggable 0.6.0 → 0.7.0
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/lib/swaggable.rb +32 -7
- data/lib/swaggable/api_validator.rb +42 -0
- data/lib/swaggable/check_body_schema.rb +72 -0
- data/lib/swaggable/check_expected_parameters.rb +26 -0
- data/lib/swaggable/check_mandatory_parameters.rb +26 -0
- data/lib/swaggable/check_request_content_type.rb +26 -0
- data/lib/swaggable/check_response_code.rb +24 -0
- data/lib/swaggable/check_response_content_type.rb +26 -0
- data/lib/swaggable/endpoint_definition.rb +30 -2
- data/lib/swaggable/endpoint_validator.rb +27 -0
- data/lib/swaggable/errors.rb +9 -0
- data/lib/swaggable/errors/validations_collection.rb +44 -0
- data/lib/swaggable/grape_adapter.rb +2 -2
- data/lib/swaggable/mime_type_definition.rb +73 -0
- data/lib/swaggable/mime_types_collection.rb +60 -0
- data/lib/swaggable/parameter_definition.rb +19 -0
- data/lib/swaggable/query_params.rb +50 -0
- data/lib/swaggable/rack_request_adapter.rb +88 -0
- data/lib/swaggable/rack_response_adapter.rb +49 -0
- data/lib/swaggable/swagger_2_serializer.rb +3 -3
- data/lib/swaggable/validating_rack_app.rb +30 -0
- data/lib/swaggable/version.rb +1 -1
- data/spec/{swaggable/integration_spec.rb → integration/dsl_spec.rb} +0 -40
- data/spec/integration/rack_app_spec.rb +44 -0
- data/spec/integration/validating_rack_app_spec.rb +50 -0
- data/spec/spec_helper.rb +9 -5
- data/spec/swaggable/api_validator_spec.rb +61 -0
- data/spec/swaggable/check_body_schema_spec.rb +94 -0
- data/spec/swaggable/check_expected_parameters_spec.rb +110 -0
- data/spec/swaggable/check_mandatory_parameters_spec.rb +110 -0
- data/spec/swaggable/check_request_content_type_spec.rb +51 -0
- data/spec/swaggable/check_response_code_spec.rb +37 -0
- data/spec/swaggable/check_response_content_type_spec.rb +51 -0
- data/spec/swaggable/endpoint_definition_spec.rb +47 -0
- data/spec/swaggable/endpoint_validator_spec.rb +102 -0
- data/spec/swaggable/errors/validations_collection_spec.rb +78 -0
- data/spec/swaggable/grape_adapter_spec.rb +2 -2
- data/spec/swaggable/mime_type_definition_spec.rb +96 -0
- data/spec/swaggable/mime_types_collection_spec.rb +83 -0
- data/spec/swaggable/parameter_definition_spec.rb +23 -0
- data/spec/swaggable/query_params_spec.rb +37 -0
- data/spec/swaggable/rack_request_adapter_spec.rb +89 -0
- data/spec/swaggable/rack_response_adapter_spec.rb +46 -0
- data/spec/swaggable/swagger_2_serializer_spec.rb +4 -2
- data/spec/swaggable/swagger_2_validator_spec.rb +2 -2
- data/spec/swaggable/validating_rack_app_spec.rb +91 -0
- data/swaggable.gemspec +1 -0
- metadata +68 -4
@@ -64,8 +64,8 @@ module Swaggable
|
|
64
64
|
api_endpoint.summary = grape_endpoint.route_description
|
65
65
|
api_endpoint.path = extract_path(grape_endpoint, grape)
|
66
66
|
|
67
|
-
api_endpoint.produces.merge grape.content_types.values
|
68
|
-
api_endpoint.consumes.merge grape.content_types.values
|
67
|
+
api_endpoint.produces.merge! grape.content_types.values
|
68
|
+
api_endpoint.consumes.merge! grape.content_types.values
|
69
69
|
|
70
70
|
api_endpoint.tags.add_new do |t|
|
71
71
|
t.name = grape.name
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class MimeTypeDefinition
|
3
|
+
def initialize id
|
4
|
+
@type, @subtype, @options = case id
|
5
|
+
when String then parse_string id
|
6
|
+
when Symbol then parse_symbol id
|
7
|
+
else raise "#{id.inspect} not valid"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
"#{type}/#{subtype}"
|
13
|
+
end
|
14
|
+
|
15
|
+
alias to_s name
|
16
|
+
|
17
|
+
def http_string
|
18
|
+
http = name
|
19
|
+
http += options if options
|
20
|
+
http
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_sym
|
24
|
+
string = if type == 'application'
|
25
|
+
subtype
|
26
|
+
else
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
string.gsub(/[-\.\/]+/,'_').to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<Swaggable::ContentTypeDefinition: #{type}/#{subtype}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
def == other
|
38
|
+
case other
|
39
|
+
when self.class then name == other.name
|
40
|
+
when String, Symbol then name == self.class.new(other).name
|
41
|
+
else false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
alias eql? ==
|
46
|
+
|
47
|
+
def hash
|
48
|
+
name.hash
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :type, :subtype, :options
|
54
|
+
|
55
|
+
def parse_string id
|
56
|
+
type, subtype, options = id.match(/(?<type>[^\/]+)\/?(?<subtype>[^;]*)(?<options>.*)/).captures
|
57
|
+
|
58
|
+
type = nil if type == ""
|
59
|
+
subtype = nil if subtype == ""
|
60
|
+
options = nil if options == ""
|
61
|
+
|
62
|
+
[type, subtype, options]
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_symbol id
|
66
|
+
type = 'application'
|
67
|
+
subtype = id.to_s.gsub('_','-')
|
68
|
+
options = nil
|
69
|
+
|
70
|
+
[type, subtype, options]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class MimeTypesCollection
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators(
|
7
|
+
:@list,
|
8
|
+
:last,
|
9
|
+
:first,
|
10
|
+
:count,
|
11
|
+
)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@list = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def << entry
|
18
|
+
entry = case entry
|
19
|
+
when Symbol, String then MimeTypeDefinition.new entry
|
20
|
+
else entry
|
21
|
+
end
|
22
|
+
|
23
|
+
if n = list.index(entry)
|
24
|
+
list[n] = entry
|
25
|
+
else
|
26
|
+
list << entry
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def each *args, &block
|
31
|
+
list.each(*args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [] key
|
35
|
+
list.detect {|e| e == key }
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<Swaggable::MimeTypesCollection: #{list.map(&:name).join(', ')}>"
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge! other
|
43
|
+
other.each {|e| self << e }
|
44
|
+
end
|
45
|
+
|
46
|
+
def == other
|
47
|
+
count == other.count && other.each {|e| include? e }
|
48
|
+
end
|
49
|
+
|
50
|
+
alias eql? ==
|
51
|
+
|
52
|
+
def hash
|
53
|
+
name.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
attr_reader :list
|
59
|
+
end
|
60
|
+
end
|
@@ -10,6 +10,7 @@ module Swaggable
|
|
10
10
|
:required,
|
11
11
|
:type,
|
12
12
|
:schema,
|
13
|
+
:value,
|
13
14
|
)
|
14
15
|
|
15
16
|
attr_enum :location, [:path, :query, :header, :body, :form, nil]
|
@@ -26,6 +27,24 @@ module Swaggable
|
|
26
27
|
)
|
27
28
|
end
|
28
29
|
|
30
|
+
def name= value
|
31
|
+
@name = value.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def == other
|
35
|
+
if other.respond_to?(:name) && other.respond_to?(:location)
|
36
|
+
[name, location] == [other.name, other.location]
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
alias eql? ==
|
43
|
+
|
44
|
+
def hash
|
45
|
+
[name, location].hash
|
46
|
+
end
|
47
|
+
|
29
48
|
private
|
30
49
|
|
31
50
|
def build_schema
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class QueryParams < Delegator
|
3
|
+
def initialize arg = nil
|
4
|
+
case arg
|
5
|
+
when String then self.string = arg
|
6
|
+
when Hash then self.hash = arg
|
7
|
+
when NilClass then self.hash = {}
|
8
|
+
else raise("#{arg.inspect} not supported. Use Hash or String")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def __getobj__
|
13
|
+
string && hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def string
|
17
|
+
@string
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
parse(string).freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def string= value
|
25
|
+
@string = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def hash= value
|
29
|
+
self.string = serialize(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def []= key, value
|
33
|
+
self.hash= hash.merge(key => value)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def serialize hash
|
39
|
+
hash.map{|k, v| "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}" }.join("&")
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse string
|
43
|
+
CGI.parse(string).inject({}){|a,h| k,v = h; a[k]=v.first; a}
|
44
|
+
end
|
45
|
+
|
46
|
+
def binding
|
47
|
+
::Kernel.binding
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Swaggable
|
5
|
+
class RackRequestAdapter
|
6
|
+
def initialize env
|
7
|
+
@env = env || raise("env hash is required")
|
8
|
+
end
|
9
|
+
|
10
|
+
def [] key
|
11
|
+
env[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def query_parameters
|
15
|
+
@query_parameters ||= QueryParams.new env['QUERY_STRING']
|
16
|
+
end
|
17
|
+
|
18
|
+
def query_parameters= p
|
19
|
+
@query_parameters = QueryParams.new p
|
20
|
+
end
|
21
|
+
|
22
|
+
def path
|
23
|
+
env['PATH_INFO']
|
24
|
+
end
|
25
|
+
|
26
|
+
def body
|
27
|
+
@body ||= if stream = env['rack.input']
|
28
|
+
string = stream.read
|
29
|
+
string == '' ? nil : string
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parsed_body
|
36
|
+
case content_type
|
37
|
+
when 'application/json' then JSON.parse body
|
38
|
+
else
|
39
|
+
raise "Don't know how to parse #{env['CONTENT_TYPE'].inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parameters endpoint = endpoint_stub
|
44
|
+
build_path_parameters(endpoint) +
|
45
|
+
build_query_parameters
|
46
|
+
end
|
47
|
+
|
48
|
+
def content_type
|
49
|
+
env['CONTENT_TYPE']
|
50
|
+
end
|
51
|
+
|
52
|
+
def content_type= value
|
53
|
+
env['CONTENT_TYPE'] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :env
|
59
|
+
|
60
|
+
def rack_request
|
61
|
+
@rack_request = Rack::Request.new env
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_query_parameters
|
65
|
+
query_parameters.map do |name, value|
|
66
|
+
ParameterDefinition.new(name: name, value: value, location: :query)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_path_parameters endpoint
|
71
|
+
if endpoint
|
72
|
+
endpoint.path_parameters_for(path).map do |name, value|
|
73
|
+
ParameterDefinition.new(name: name, value: value, location: :path)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def endpoint_stub
|
81
|
+
Object.new.tap do |o|
|
82
|
+
def o.path_parameters_for _
|
83
|
+
{}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class RackResponseAdapter
|
3
|
+
def initialize rack_response = default_rack_response
|
4
|
+
@rack_response = rack_response
|
5
|
+
end
|
6
|
+
|
7
|
+
def content_type
|
8
|
+
rack_headers['Content-Type']
|
9
|
+
end
|
10
|
+
|
11
|
+
def content_type= value
|
12
|
+
rack_headers['Content-Type'] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def code
|
16
|
+
rack_response[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
def code= value
|
20
|
+
rack_response[0] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def == other
|
24
|
+
if other.respond_to? :rack_response, true
|
25
|
+
rack_response == other.rack_response
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias eql? ==
|
32
|
+
|
33
|
+
def hash
|
34
|
+
rack_response.hash
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
attr_accessor :rack_response
|
40
|
+
|
41
|
+
def default_rack_response
|
42
|
+
[200, {}, []]
|
43
|
+
end
|
44
|
+
|
45
|
+
def rack_headers
|
46
|
+
rack_response[1]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -44,7 +44,7 @@ module Swaggable
|
|
44
44
|
def serialize_endpoints endpoints
|
45
45
|
endpoints.inject({}) do |out, endpoint|
|
46
46
|
out[endpoint.path] ||= {}
|
47
|
-
out[endpoint.path][endpoint.verb] = serialize_endpoint(endpoint)
|
47
|
+
out[endpoint.path][endpoint.verb.downcase.to_sym] = serialize_endpoint(endpoint)
|
48
48
|
out
|
49
49
|
end
|
50
50
|
end
|
@@ -52,8 +52,8 @@ module Swaggable
|
|
52
52
|
def serialize_endpoint endpoint
|
53
53
|
{
|
54
54
|
tags: endpoint.tags.map(&:name),
|
55
|
-
consumes: endpoint.consumes.
|
56
|
-
produces: endpoint.produces.
|
55
|
+
consumes: endpoint.consumes.map(&:http_string),
|
56
|
+
produces: endpoint.produces.map(&:http_string),
|
57
57
|
parameters: endpoint.parameters.map{|p| serialize_parameter p },
|
58
58
|
responses: serialize_responses(endpoint.responses),
|
59
59
|
}.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class ValidatingRackApp
|
3
|
+
attr_accessor(
|
4
|
+
:app,
|
5
|
+
:definition,
|
6
|
+
)
|
7
|
+
|
8
|
+
def initialize args = {}
|
9
|
+
@app = args[:app]
|
10
|
+
@definition = args[:definition]
|
11
|
+
end
|
12
|
+
|
13
|
+
def call rack_req
|
14
|
+
req = RackRequestAdapter.new rack_req
|
15
|
+
|
16
|
+
validator = ApiValidator.new definition: definition, request: req
|
17
|
+
|
18
|
+
errors = validator.errors_for_request
|
19
|
+
raise(errors) if errors.any?
|
20
|
+
|
21
|
+
rack_resp = app.call rack_req
|
22
|
+
resp = RackResponseAdapter.new rack_resp
|
23
|
+
|
24
|
+
errors = validator.errors_for_response resp
|
25
|
+
raise(errors) if errors.any?
|
26
|
+
|
27
|
+
rack_resp
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/swaggable/version.rb
CHANGED
@@ -1,46 +1,6 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
|
-
require 'rack/test'
|
3
|
-
require 'grape'
|
4
2
|
|
5
3
|
RSpec.describe 'Integration' do
|
6
|
-
context 'RackApp' do
|
7
|
-
include Rack::Test::Methods
|
8
|
-
|
9
|
-
def app
|
10
|
-
subject
|
11
|
-
end
|
12
|
-
|
13
|
-
subject { rack_app }
|
14
|
-
let(:rack_app) { Swaggable::RackApp.new(api_definition: api_def) }
|
15
|
-
let(:api_def) { Swaggable::ApiDefinition.from_grape_api(grape_api) }
|
16
|
-
|
17
|
-
let(:grape_api) do
|
18
|
-
Class.new(Grape::API).tap do |g|
|
19
|
-
g.params do
|
20
|
-
requires :user_uuid
|
21
|
-
end
|
22
|
-
|
23
|
-
g.get '/' do
|
24
|
-
status 200
|
25
|
-
body({some: 'body'})
|
26
|
-
end
|
27
|
-
|
28
|
-
end.tap do |grape|
|
29
|
-
stub_const('MyGrapeApi', grape)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'has status 200 OK' do
|
34
|
-
get '/'
|
35
|
-
expect(last_response.status).to eq 200
|
36
|
-
expect(last_response.headers['Content-Type']).to eq 'application/json'
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'validates' do
|
40
|
-
expect(subject.validate!).to be true
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
4
|
context 'dsl' do
|
45
5
|
let(:rack_app) { Swaggable::RackApp.new(api_definition: api) }
|
46
6
|
|