sinatra-swagger-exposer 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/Gemfile +0 -1
  4. data/README.md +1 -0
  5. data/lib/sinatra/swagger-exposer/configuration/swagger-configuration-utilities.rb +124 -0
  6. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint-parameter.rb +15 -20
  7. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint-response.rb +39 -7
  8. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint.rb +21 -8
  9. data/lib/sinatra/swagger-exposer/configuration/swagger-hash-like.rb +45 -0
  10. data/lib/sinatra/swagger-exposer/configuration/swagger-info.rb +9 -8
  11. data/lib/sinatra/swagger-exposer/configuration/swagger-response-header.rb +68 -0
  12. data/lib/sinatra/swagger-exposer/configuration/swagger-response-headers.rb +33 -0
  13. data/lib/sinatra/swagger-exposer/configuration/swagger-type-property.rb +7 -6
  14. data/lib/sinatra/swagger-exposer/configuration/swagger-type.rb +10 -9
  15. data/lib/sinatra/swagger-exposer/configuration/swagger-types.rb +4 -20
  16. data/lib/sinatra/swagger-exposer/processing/swagger-array-value-processor.rb +46 -0
  17. data/lib/sinatra/swagger-exposer/processing/{swagger-base-value-preprocessor.rb → swagger-base-value-processor.rb} +9 -7
  18. data/lib/sinatra/swagger-exposer/processing/{swagger-parameter-preprocessor.rb → swagger-parameter-processor.rb} +9 -9
  19. data/lib/sinatra/swagger-exposer/processing/{swagger-primitive-value-preprocessor.rb → swagger-primitive-value-processor.rb} +46 -46
  20. data/lib/sinatra/swagger-exposer/processing/{swagger-preprocessor-dispatcher.rb → swagger-processor-dispatcher.rb} +11 -11
  21. data/lib/sinatra/swagger-exposer/processing/swagger-request-processor.rb +123 -0
  22. data/lib/sinatra/swagger-exposer/processing/swagger-response-processor.rb +47 -0
  23. data/lib/sinatra/swagger-exposer/processing/swagger-type-value-processor.rb +37 -0
  24. data/lib/sinatra/swagger-exposer/swagger-content-creator.rb +3 -7
  25. data/lib/sinatra/swagger-exposer/swagger-exposer.rb +99 -33
  26. data/lib/sinatra/swagger-exposer/swagger-parameter-helper.rb +19 -19
  27. data/lib/sinatra/swagger-exposer/swagger-request-processor-creator.rb +180 -0
  28. data/lib/sinatra/swagger-exposer/version.rb +1 -1
  29. data/sinatra-swagger-exposer.gemspec +9 -8
  30. metadata +29 -11
  31. data/lib/sinatra/swagger-exposer/processing/swagger-array-value-preprocessor.rb +0 -46
  32. data/lib/sinatra/swagger-exposer/processing/swagger-request-preprocessor.rb +0 -64
  33. data/lib/sinatra/swagger-exposer/processing/swagger-type-value-preprocessor.rb +0 -37
  34. data/lib/sinatra/swagger-exposer/swagger-preprocessor-creator.rb +0 -137
  35. data/lib/sinatra/swagger-exposer/swagger-utilities.rb +0 -108
@@ -6,23 +6,23 @@ module Sinatra
6
6
 
7
7
  module Processing
8
8
 
9
- # Dispatch content to a preprocessor
10
- class SwaggerPreprocessorDispatcher
9
+ # Dispatch content to a processor
10
+ class SwaggerProcessorDispatcher
11
11
 
12
- attr_reader :how_to_pass, :preprocessor
12
+ attr_reader :how_to_pass, :processor
13
13
 
14
14
  include Sinatra::SwaggerExposer::SwaggerParameterHelper
15
15
 
16
16
  # Initialize
17
- # @param how_to_pass how the value should be passed to the preprocessor
18
- # @param preprocessor [Sinatra::SwaggerExposer::Processing::SwaggerBaseValuePreprocessor] processor for the values
19
- def initialize(how_to_pass, preprocessor)
17
+ # @param how_to_pass how the value should be passed to the processor
18
+ # @param processor [Sinatra::SwaggerExposer::Processing::SwaggerBaseValueProcessor] processor for the values
19
+ def initialize(how_to_pass, processor)
20
20
  @how_to_pass = how_to_pass
21
- @preprocessor = preprocessor
21
+ @processor = processor
22
22
  end
23
23
 
24
24
  def useful?
25
- (@how_to_pass != HOW_TO_PASS_PATH) && @preprocessor.useful?
25
+ (@how_to_pass != HOW_TO_PASS_PATH) && @processor.useful?
26
26
  end
27
27
 
28
28
  # Process the value
@@ -31,11 +31,11 @@ module Sinatra
31
31
  when HOW_TO_PASS_PATH
32
32
  # can't validate
33
33
  when HOW_TO_PASS_QUERY
34
- @preprocessor.process(app.params)
34
+ @processor.process(app.params)
35
35
  when HOW_TO_PASS_HEADER
36
- @preprocessor.process(app.headers)
36
+ @processor.process(app.headers)
37
37
  when HOW_TO_PASS_BODY
38
- @preprocessor.process(parsed_body || {})
38
+ @processor.process(parsed_body || {})
39
39
  end
40
40
  end
41
41
 
@@ -0,0 +1,123 @@
1
+ require 'json'
2
+ require 'mime-types'
3
+
4
+ require_relative '../swagger-invalid-exception'
5
+
6
+ module Sinatra
7
+
8
+ module SwaggerExposer
9
+
10
+ module Processing
11
+
12
+ # Custom mime types that matches everything when '*' is in list
13
+ class AllMimesTypes
14
+
15
+ def like?(other)
16
+ true
17
+ end
18
+
19
+ end
20
+
21
+ # A processor for a request, apply the parameters processors then execute the query code
22
+ class SwaggerRequestProcessor
23
+
24
+ attr_reader :processors_dispatchers, :response_processors, :produces
25
+
26
+ # @param produces [Array<String>]
27
+ def initialize(produces = nil)
28
+ @processors_dispatchers = []
29
+ @response_processors = {}
30
+ @produces = produces
31
+ if produces
32
+ @produces_types = produces.collect do |produce|
33
+ if produce == '*'
34
+ [Sinatra::SwaggerExposer::Processing::AllMimesTypes.new]
35
+ else
36
+ MIME::Types[produce]
37
+ end
38
+ end.flatten
39
+ end
40
+ end
41
+
42
+ # @param dispatcher [Sinatra::SwaggerExposer::Processing::SwaggerProcessorDispatcher]
43
+ def add_dispatcher(dispatcher)
44
+ @processors_dispatchers << dispatcher
45
+ end
46
+
47
+ # @param response_processor [Sinatra::SwaggerExposer::Processing::SwaggerResponseProcessor]
48
+ def add_response_processor(code, response_processor)
49
+ @response_processors[code] = response_processor
50
+ end
51
+
52
+ JSON_CONTENT_TYPE = MIME::Types['application/json'].first
53
+
54
+ # Run the processor the call the route content
55
+ # @param app the sinatra app being run
56
+ # @param block_params [Array] the block parameters
57
+ # @param block the block containing the route content
58
+ def run(app, block_params, &block)
59
+ parsed_body = {}
60
+ if JSON_CONTENT_TYPE.like?(app.env['CONTENT_TYPE'])
61
+ body = app.request.body.read
62
+ unless body.empty?
63
+ begin
64
+ parsed_body = JSON.parse(body)
65
+ rescue JSON::ParserError => e
66
+ return [400, {:code => 400, :message => e.message}.to_json]
67
+ end
68
+ end
69
+ end
70
+ app.params['parsed_body'] = parsed_body
71
+ unless @processors_dispatchers.empty?
72
+ @processors_dispatchers.each do |processor_dispatcher|
73
+ begin
74
+ processor_dispatcher.process(app, parsed_body)
75
+ rescue SwaggerInvalidException => e
76
+ app.content_type :json
77
+ return [400, {:code => 400, :message => e.message}.to_json]
78
+ end
79
+ end
80
+ end
81
+ if block
82
+ # Execute the block in the context of the app
83
+ app.instance_exec(*block_params, &block)
84
+ else
85
+ ''
86
+ end
87
+ end
88
+
89
+ # Validate the response
90
+ # @param response_body [String] the body
91
+ # @param content_type [String] the content type
92
+ # @param response_status [Integer] the status
93
+ def validate_response(response_body, content_type, response_status)
94
+ validate_response_content_type(content_type, response_status)
95
+ if @response_processors.key?(response_status)
96
+ response_processor = response_processors[response_status]
97
+ if JSON_CONTENT_TYPE.like?(content_type) && response_processor
98
+ response_processor.validate_response(response_body)
99
+ end
100
+ else
101
+ raise SwaggerInvalidException.new("Status with unknown response status [#{response_status}], known statuses are [#{@response_processors.keys.join(', ')}] response value is #{response_body}")
102
+ end
103
+ end
104
+
105
+ # Validate a response content type
106
+ # @param content_type [String] the content type to validate
107
+ # @param response_status [Integer] the status
108
+ def validate_response_content_type(content_type, response_status)
109
+ if content_type.nil? && (response_status == 204)
110
+ # No content and no content type => everything is OK
111
+ elsif @produces
112
+ unless @produces_types.any? { |produce| produce.like?(content_type) }
113
+ raise SwaggerInvalidException.new("Undeclared content type [#{content_type}], declared content type are [#{@produces.join(', ')}]")
114
+ end
115
+ elsif !JSON_CONTENT_TYPE.like?(content_type)
116
+ raise SwaggerInvalidException.new("Undeclared content type [#{content_type}], if no declaration for the endpoint you should only return json")
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../swagger-parameter-helper'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ module Processing
8
+
9
+ # Process a response
10
+ class SwaggerResponseProcessor
11
+
12
+ include Sinatra::SwaggerExposer::SwaggerParameterHelper
13
+
14
+ attr_reader :endpoint_response, :processor
15
+
16
+ # Initialize
17
+ # @param endpoint_response [Sinatra::SwaggerExposer::Configuration::SwaggerEndpointResponse]
18
+ # @param processor [Sinatra::SwaggerExposer::Processing::SwaggerTypeValueProcessor]
19
+ def initialize(endpoint_response, processor)
20
+ @endpoint_response = endpoint_response
21
+ @processor = processor
22
+ end
23
+
24
+ # Test if the processor is useful
25
+ # @return [TrueClass]
26
+ def useful?
27
+ (@endpoint_response && (@endpoint_response.type != TYPE_FILE)) || @processor
28
+ end
29
+
30
+ # Validate a response
31
+ # @param response_body [String] the body
32
+ def validate_response(response_body)
33
+ parsed_response_body = nil
34
+ begin
35
+ parsed_response_body = JSON.parse(response_body)
36
+ rescue JSON::ParserError => e
37
+ raise SwaggerInvalidException.new("Response is not a valid json [#{response_body}]")
38
+ end
39
+ if @processor
40
+ @processor.validate_value(parsed_response_body)
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'swagger-base-value-processor'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ module Processing
8
+
9
+ # A processor for a type parameter
10
+ class SwaggerTypeValueProcessor < SwaggerBaseValueProcessor
11
+
12
+ attr_reader :attributes_processors
13
+
14
+ # Initialize
15
+ # @param name [String] the name
16
+ # @param required [TrueClass] if the parameter is required
17
+ # @param attributes_processors [Array<Sinatra::SwaggerExposer::Processing::SwaggerBaseValueProcessor>] the attributes processors
18
+ def initialize(name, required, attributes_processors)
19
+ super(name, required, nil)
20
+ @attributes_processors = attributes_processors
21
+ end
22
+
23
+ def useful?
24
+ super || (!(@attributes_processors.empty?))
25
+ end
26
+
27
+ def validate_value(value)
28
+ @attributes_processors.each do |attribute_processor|
29
+ attribute_processor.process(value)
30
+ end
31
+ value
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,3 @@
1
- require_relative 'swagger-utilities'
2
-
3
1
  module Sinatra
4
2
 
5
3
  module SwaggerExposer
@@ -7,8 +5,6 @@ module Sinatra
7
5
  # Create the swagger content
8
6
  class SwaggerContentCreator
9
7
 
10
- include SwaggerUtilities
11
-
12
8
  def initialize(swagger_info, swagger_types, swagger_endpoints)
13
9
  @swagger_info = swagger_info
14
10
  @swagger_types = swagger_types
@@ -17,9 +13,9 @@ module Sinatra
17
13
 
18
14
  def to_swagger
19
15
  result = {
20
- swagger: '2.0',
21
- consumes: ['application/json'],
22
- produces: ['application/json'],
16
+ swagger: '2.0',
17
+ consumes: ['application/json'],
18
+ produces: ['application/json'],
23
19
  }
24
20
  if @swagger_info
25
21
  result[:info] = @swagger_info.to_swagger
@@ -4,36 +4,48 @@ require 'json'
4
4
  require_relative 'configuration/swagger-endpoint'
5
5
  require_relative 'configuration/swagger-endpoint-parameter'
6
6
  require_relative 'configuration/swagger-endpoint-response'
7
+ require_relative 'configuration/swagger-response-headers'
7
8
  require_relative 'configuration/swagger-info'
8
9
  require_relative 'configuration/swagger-types'
9
10
  require_relative 'swagger-content-creator'
10
11
  require_relative 'swagger-invalid-exception'
11
- require_relative 'swagger-preprocessor-creator'
12
+ require_relative 'swagger-request-processor-creator'
12
13
 
13
14
  module Sinatra
14
15
 
15
16
  # Expose swagger API from your Sinatra app
16
17
  module SwaggerExposer
17
18
 
19
+ # Called when we register the extension
20
+ # @param app [Sinatra::Base]
18
21
  def self.registered(app)
22
+ app.set :result_validation, (ENV['RACK_ENV'] != 'production')
19
23
  app.set :swagger_endpoints, []
20
24
  app.set :swagger_current_endpoint_info, {}
21
25
  app.set :swagger_current_endpoint_parameters, {}
22
26
  app.set :swagger_current_endpoint_responses, {}
27
+
23
28
  swagger_types = Sinatra::SwaggerExposer::Configuration::SwaggerTypes.new
24
29
  app.set :swagger_types, swagger_types
25
- app.set :swagger_preprocessor_creator, Sinatra::SwaggerExposer::SwaggerPreprocessorCreator.new(swagger_types)
26
- declare_swagger_endpoints(app)
30
+
31
+ response_headers = Sinatra::SwaggerExposer::Configuration::SwaggerResponseHeaders.new
32
+ app.set :swagger_response_headers, response_headers
33
+
34
+ app.set :swagger_processor_creator, Sinatra::SwaggerExposer::SwaggerProcessorCreator.new(swagger_types)
35
+ declare_swagger_endpoint(app)
27
36
  end
28
37
 
29
- def self.declare_swagger_endpoints(app)
38
+ # Declare the endpoint to serve the swagger content
39
+ # @param app [Sinatra::Base]
40
+ def self.declare_swagger_endpoint(app)
30
41
  app.endpoint_summary 'The swagger endpoint'
31
42
  app.endpoint_tags 'swagger'
32
- app.get '/swagger_doc.json' do
43
+ app.endpoint_response 200
44
+ app.get('/swagger_doc.json') do
33
45
  swagger_content = Sinatra::SwaggerExposer::SwaggerContentCreator.new(
34
- settings.respond_to?(:swagger_info) ? settings.swagger_info : nil,
35
- settings.swagger_types,
36
- settings.swagger_endpoints
46
+ settings.respond_to?(:swagger_info) ? settings.swagger_info : nil,
47
+ settings.swagger_types,
48
+ settings.swagger_endpoints
37
49
  ).to_swagger
38
50
  content_type :json
39
51
  swagger_content.to_json
@@ -41,32 +53,40 @@ module Sinatra
41
53
 
42
54
  app.endpoint_summary 'Option method for the swagger endpoint, useful for some CORS stuff'
43
55
  app.endpoint_tags 'swagger'
44
- app.options '/swagger_doc.json' do
56
+ app.endpoint_response 200
57
+ app.endpoint_produces 'text/plain;charset=utf-8'
58
+ app.options('/swagger_doc.json') do
59
+ content_type :text
45
60
  200
46
61
  end
47
62
  end
48
63
 
49
64
  # Provide a summary for the endpoint
65
+ # @param summary [String]
50
66
  def endpoint_summary(summary)
51
67
  set_if_type_and_not_exist(summary, :summary, String)
52
68
  end
53
69
 
54
70
  # Provide a path
71
+ # @param path [String]
55
72
  def endpoint_path(path)
56
73
  set_if_type_and_not_exist(path, :path, String)
57
74
  end
58
75
 
59
76
  # Provide a description for the endpoint
77
+ # @param description [String]
60
78
  def endpoint_description(description)
61
79
  set_if_type_and_not_exist(description, :description, String)
62
80
  end
63
81
 
64
82
  # Provide tags for the endpoint
83
+ # @param tags [Array<String>]
65
84
  def endpoint_tags(*tags)
66
85
  set_if_not_exist(tags, :tags)
67
86
  end
68
87
 
69
88
  # Provide produces params for the endpoint
89
+ # @param produces [Array<String>] the response types
70
90
  def endpoint_produces(*produces)
71
91
  set_if_not_exist(produces, :produces)
72
92
  end
@@ -76,13 +96,13 @@ module Sinatra
76
96
  parameters = settings.swagger_current_endpoint_parameters
77
97
  check_if_not_duplicate(name, parameters, 'Parameter')
78
98
  parameters[name] = Sinatra::SwaggerExposer::Configuration::SwaggerEndpointParameter.new(
79
- name,
80
- description,
81
- how_to_pass,
82
- required,
83
- type,
84
- params,
85
- settings.swagger_types.types_names)
99
+ name,
100
+ description,
101
+ how_to_pass,
102
+ required,
103
+ type,
104
+ params,
105
+ settings.swagger_types.types_names)
86
106
  end
87
107
 
88
108
  # Define fluent endpoint dispatcher
@@ -124,20 +144,62 @@ module Sinatra
124
144
  settings.swagger_types.add_type(name, params)
125
145
  end
126
146
 
147
+ # Declare a response header
148
+ def response_header(name, type, description)
149
+ settings.swagger_response_headers.add_response_header(name, type, description)
150
+ end
151
+
127
152
  # Declare a response
128
- def endpoint_response(code, type = nil, description = nil)
153
+ # @param code [Integer] the response code
154
+ # @param type the type
155
+ # @param description [String] the description
156
+ # @param headers [Array<String>] the headers names
157
+ def endpoint_response(code, type = nil, description = nil, headers = [])
129
158
  responses = settings.swagger_current_endpoint_responses
130
159
  check_if_not_duplicate(code, responses, 'Response')
131
- responses[code] = Sinatra::SwaggerExposer::Configuration::SwaggerEndpointResponse.new(type, description, settings.swagger_types.types_names)
160
+ responses[code] = Sinatra::SwaggerExposer::Configuration::SwaggerEndpointResponse.new(
161
+ type,
162
+ description,
163
+ settings.swagger_types.types_names,
164
+ headers,
165
+ settings.swagger_response_headers
166
+ )
132
167
  end
133
168
 
169
+ # Override Sinatra route method
134
170
  def route(verb, path, options = {}, &block)
135
- if verb == 'HEAD'
171
+ no_swagger = options[:no_swagger]
172
+ options.delete(:no_swagger)
173
+ if (verb == 'HEAD') || no_swagger
136
174
  super(verb, path, options, &block)
137
175
  else
138
- request_preprocessor = process_endpoint(verb.downcase, path, options)
176
+ request_processor = create_request_processor(verb.downcase, path, options)
139
177
  super(verb, path, options) do |*params|
140
- request_preprocessor.run(self, params, &block)
178
+ response = catch(:halt) do
179
+ request_processor.run(self, params, &block)
180
+ end
181
+ if settings.result_validation
182
+ begin
183
+ # Inspired from Sinatra#invoke
184
+ if (Fixnum === response) or (String === response)
185
+ response = [response]
186
+ end
187
+ if (Array === response) and (Fixnum === response.first)
188
+ response_for_validation = response.dup
189
+ response_status = response_for_validation.shift
190
+ response_body = response_for_validation.pop
191
+ response_headers = (response_for_validation.pop || {}).merge(self.response.header)
192
+ response_content_type = response_headers['Content-Type']
193
+ request_processor.validate_response(response_body, response_content_type, response_status)
194
+ elsif response.respond_to? :each
195
+ request_processor.validate_response(response.first.dup, self.response.header['Content-Type'], 200)
196
+ end
197
+ rescue Sinatra::SwaggerExposer::SwaggerInvalidException => e
198
+ content_type :json
199
+ throw :halt, [400, {:code => 400, :message => e.message}.to_json]
200
+ end
201
+ end
202
+ throw :halt, response
141
203
  end
142
204
  end
143
205
  end
@@ -145,26 +207,26 @@ module Sinatra
145
207
  private
146
208
 
147
209
  # Call for each endpoint declaration
148
- # @return [SwaggerRequestPreprocessor]
149
- def process_endpoint(type, path, opts)
210
+ # @return [Sinatra::SwaggerExposer::Processing::SwaggerRequestProcessor]
211
+ def create_request_processor(type, path, opts)
150
212
  current_endpoint_info = settings.swagger_current_endpoint_info
151
213
  current_endpoint_parameters = settings.swagger_current_endpoint_parameters
152
214
  current_endpoint_responses = settings.swagger_current_endpoint_responses
153
215
  endpoint = Sinatra::SwaggerExposer::Configuration::SwaggerEndpoint.new(
154
- type,
155
- path,
156
- current_endpoint_parameters.values,
157
- current_endpoint_responses.clone,
158
- current_endpoint_info[:summary],
159
- current_endpoint_info[:description],
160
- current_endpoint_info[:tags],
161
- current_endpoint_info[:path],
162
- current_endpoint_info[:produces])
216
+ type,
217
+ path,
218
+ current_endpoint_parameters.values,
219
+ current_endpoint_responses.clone,
220
+ current_endpoint_info[:summary],
221
+ current_endpoint_info[:description],
222
+ current_endpoint_info[:tags],
223
+ current_endpoint_info[:path],
224
+ current_endpoint_info[:produces])
163
225
  settings.swagger_endpoints << endpoint
164
226
  current_endpoint_info.clear
165
227
  current_endpoint_parameters.clear
166
228
  current_endpoint_responses.clear
167
- settings.swagger_preprocessor_creator.create_endpoint_processor(endpoint)
229
+ settings.swagger_processor_creator.create_request_processor(endpoint)
168
230
  end
169
231
 
170
232
  def set_if_not_exist(value, name)
@@ -181,6 +243,10 @@ module Sinatra
181
243
  set_if_not_exist(value, name)
182
244
  end
183
245
 
246
+ # Check if a value does not exist in a hash and throw an Exception
247
+ # @param key the key to validate
248
+ # @param values [Hash] the existing keys
249
+ # @param name [String] the valud name for the error message
184
250
  def check_if_not_duplicate(key, values, name)
185
251
  if values.key? key
186
252
  raise SwaggerInvalidException.new("#{name} already exist for #{key} with value [#{values[key]}]")