swaggable 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swaggable.rb +32 -7
  3. data/lib/swaggable/api_validator.rb +42 -0
  4. data/lib/swaggable/check_body_schema.rb +72 -0
  5. data/lib/swaggable/check_expected_parameters.rb +26 -0
  6. data/lib/swaggable/check_mandatory_parameters.rb +26 -0
  7. data/lib/swaggable/check_request_content_type.rb +26 -0
  8. data/lib/swaggable/check_response_code.rb +24 -0
  9. data/lib/swaggable/check_response_content_type.rb +26 -0
  10. data/lib/swaggable/endpoint_definition.rb +30 -2
  11. data/lib/swaggable/endpoint_validator.rb +27 -0
  12. data/lib/swaggable/errors.rb +9 -0
  13. data/lib/swaggable/errors/validations_collection.rb +44 -0
  14. data/lib/swaggable/grape_adapter.rb +2 -2
  15. data/lib/swaggable/mime_type_definition.rb +73 -0
  16. data/lib/swaggable/mime_types_collection.rb +60 -0
  17. data/lib/swaggable/parameter_definition.rb +19 -0
  18. data/lib/swaggable/query_params.rb +50 -0
  19. data/lib/swaggable/rack_request_adapter.rb +88 -0
  20. data/lib/swaggable/rack_response_adapter.rb +49 -0
  21. data/lib/swaggable/swagger_2_serializer.rb +3 -3
  22. data/lib/swaggable/validating_rack_app.rb +30 -0
  23. data/lib/swaggable/version.rb +1 -1
  24. data/spec/{swaggable/integration_spec.rb → integration/dsl_spec.rb} +0 -40
  25. data/spec/integration/rack_app_spec.rb +44 -0
  26. data/spec/integration/validating_rack_app_spec.rb +50 -0
  27. data/spec/spec_helper.rb +9 -5
  28. data/spec/swaggable/api_validator_spec.rb +61 -0
  29. data/spec/swaggable/check_body_schema_spec.rb +94 -0
  30. data/spec/swaggable/check_expected_parameters_spec.rb +110 -0
  31. data/spec/swaggable/check_mandatory_parameters_spec.rb +110 -0
  32. data/spec/swaggable/check_request_content_type_spec.rb +51 -0
  33. data/spec/swaggable/check_response_code_spec.rb +37 -0
  34. data/spec/swaggable/check_response_content_type_spec.rb +51 -0
  35. data/spec/swaggable/endpoint_definition_spec.rb +47 -0
  36. data/spec/swaggable/endpoint_validator_spec.rb +102 -0
  37. data/spec/swaggable/errors/validations_collection_spec.rb +78 -0
  38. data/spec/swaggable/grape_adapter_spec.rb +2 -2
  39. data/spec/swaggable/mime_type_definition_spec.rb +96 -0
  40. data/spec/swaggable/mime_types_collection_spec.rb +83 -0
  41. data/spec/swaggable/parameter_definition_spec.rb +23 -0
  42. data/spec/swaggable/query_params_spec.rb +37 -0
  43. data/spec/swaggable/rack_request_adapter_spec.rb +89 -0
  44. data/spec/swaggable/rack_response_adapter_spec.rb +46 -0
  45. data/spec/swaggable/swagger_2_serializer_spec.rb +4 -2
  46. data/spec/swaggable/swagger_2_validator_spec.rb +2 -2
  47. data/spec/swaggable/validating_rack_app_spec.rb +91 -0
  48. data/swaggable.gemspec +1 -0
  49. 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.to_a,
56
- produces: endpoint.produces.to_a,
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
@@ -1,3 +1,3 @@
1
1
  module Swaggable
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
@@ -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