sinatra-swagger-exposer 0.3.0 → 0.4.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 (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]}]")