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