sinatra-swagger-exposer-ng 0.5.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 +7 -0
  2. data/CHANGELOG.md +37 -0
  3. data/CODE_OF_CONDUCT.md +13 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +118 -0
  7. data/Rakefile +9 -0
  8. data/lib/sinatra/swagger-exposer/configuration/swagger-configuration-utilities.rb +124 -0
  9. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint-parameter.rb +113 -0
  10. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint-response.rb +96 -0
  11. data/lib/sinatra/swagger-exposer/configuration/swagger-endpoint.rb +101 -0
  12. data/lib/sinatra/swagger-exposer/configuration/swagger-hash-like.rb +45 -0
  13. data/lib/sinatra/swagger-exposer/configuration/swagger-info.rb +73 -0
  14. data/lib/sinatra/swagger-exposer/configuration/swagger-parameter-validation-helper.rb +106 -0
  15. data/lib/sinatra/swagger-exposer/configuration/swagger-response-header.rb +68 -0
  16. data/lib/sinatra/swagger-exposer/configuration/swagger-response-headers.rb +33 -0
  17. data/lib/sinatra/swagger-exposer/configuration/swagger-type-property.rb +83 -0
  18. data/lib/sinatra/swagger-exposer/configuration/swagger-type.rb +128 -0
  19. data/lib/sinatra/swagger-exposer/configuration/swagger-types.rb +35 -0
  20. data/lib/sinatra/swagger-exposer/processing/swagger-array-value-processor.rb +46 -0
  21. data/lib/sinatra/swagger-exposer/processing/swagger-base-value-processor.rb +50 -0
  22. data/lib/sinatra/swagger-exposer/processing/swagger-file-processor-dispatcher.rb +35 -0
  23. data/lib/sinatra/swagger-exposer/processing/swagger-primitive-value-processor.rb +165 -0
  24. data/lib/sinatra/swagger-exposer/processing/swagger-processor-dispatcher.rb +69 -0
  25. data/lib/sinatra/swagger-exposer/processing/swagger-request-processor.rb +128 -0
  26. data/lib/sinatra/swagger-exposer/processing/swagger-response-processor.rb +47 -0
  27. data/lib/sinatra/swagger-exposer/processing/swagger-type-value-processor.rb +37 -0
  28. data/lib/sinatra/swagger-exposer/swagger-content-creator.rb +55 -0
  29. data/lib/sinatra/swagger-exposer/swagger-exposer.rb +258 -0
  30. data/lib/sinatra/swagger-exposer/swagger-invalid-exception.rb +16 -0
  31. data/lib/sinatra/swagger-exposer/swagger-parameter-helper.rb +73 -0
  32. data/lib/sinatra/swagger-exposer/swagger-request-processor-creator.rb +188 -0
  33. data/lib/sinatra/swagger-exposer/version.rb +5 -0
  34. data/sinatra-swagger-exposer.gemspec +30 -0
  35. metadata +176 -0
@@ -0,0 +1,35 @@
1
+ require_relative '../swagger-parameter-helper'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ module Processing
8
+
9
+ # Fake dispatcher for files
10
+ class SwaggerFileProcessorDispatcher
11
+
12
+ # Initialize
13
+ # @param name [String] the name
14
+ # @param required [TrueClass] if the parameter is required
15
+ def initialize(name, required)
16
+ @name = name
17
+ @required = required
18
+ end
19
+
20
+ def useful?
21
+ @required
22
+ end
23
+
24
+ # Process the value
25
+ def process(app, parsed_body)
26
+ if app.params.key?(@name.to_s) && (!app.params[@name.to_s].nil?)
27
+ elsif @required
28
+ raise SwaggerInvalidException.new("Mandatory value [#{@name}] is missing")
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,165 @@
1
+ require 'date'
2
+
3
+ require_relative 'swagger-base-value-processor'
4
+ require_relative '../swagger-parameter-helper'
5
+ require_relative '../swagger-invalid-exception'
6
+
7
+ module Sinatra
8
+
9
+ module SwaggerExposer
10
+
11
+ module Processing
12
+
13
+ # Validate primitive value
14
+ class SwaggerPrimitiveValueProcessor < SwaggerBaseValueProcessor
15
+
16
+ include Sinatra::SwaggerExposer::SwaggerParameterHelper
17
+
18
+ attr_reader :type, :params
19
+
20
+ # Initialize
21
+ # @param name [String] the name
22
+ # @param required [TrueClass] if the parameter is required
23
+ # @param type [String] the type name
24
+ # @param default [Object] the default value
25
+ # @param params [Hash] parameters
26
+ def initialize(name, required, type, default, params)
27
+ super(name, required, default)
28
+ @type = type
29
+ @params = params
30
+ end
31
+
32
+ def useful?
33
+ super ||
34
+ [TYPE_NUMBER, TYPE_INTEGER, TYPE_BOOLEAN, TYPE_DATE_TIME].include?(@type) || # Must check type
35
+ (@params.key? PARAMS_MIN_LENGTH) || (@params.key? PARAMS_MAX_LENGTH) # Must check string
36
+ end
37
+
38
+ # Dispatch method
39
+ def validate_value(value)
40
+ case @type
41
+ when TYPE_NUMBER
42
+ return validate_value_number(value)
43
+ when TYPE_INTEGER
44
+ return validate_value_integer(value)
45
+ when TYPE_BOOLEAN
46
+ return validate_value_boolean(value)
47
+ when TYPE_DATE_TIME
48
+ return validate_value_date_time(value)
49
+ else
50
+ return validate_value_string(value)
51
+ end
52
+ end
53
+
54
+ # Validate a boolean
55
+ def validate_value_boolean(value)
56
+ if (value == 'true') || value.is_a?(TrueClass)
57
+ true
58
+ elsif (value == 'false') || value.is_a?(FalseClass)
59
+ false
60
+ else
61
+ raise SwaggerInvalidException.new("Value [#{name}] should be an boolean but is [#{value}]")
62
+ end
63
+ end
64
+
65
+ # Validate an integer
66
+ def validate_value_integer(value)
67
+ begin
68
+ f = Float(value)
69
+ i = Integer(value)
70
+ if f == i
71
+ i
72
+ else
73
+ raise SwaggerInvalidException.new("Value [#{name}] should be an integer but is [#{value}]")
74
+ end
75
+ value = Integer(value)
76
+ validate_numerical_value(value)
77
+ value
78
+ rescue ArgumentError
79
+ raise SwaggerInvalidException.new("Value [#{name}] should be an integer but is [#{value}]")
80
+ rescue TypeError
81
+ raise SwaggerInvalidException.new("Value [#{name}] should be an integer but is [#{value}]")
82
+ end
83
+ end
84
+
85
+ # Validate a number value
86
+ def validate_value_number(value)
87
+ begin
88
+ value = Float(value)
89
+ validate_numerical_value(value)
90
+ return value
91
+ rescue ArgumentError
92
+ raise SwaggerInvalidException.new("Value [#{name}] should be a float but is [#{value}]")
93
+ rescue TypeError
94
+ raise SwaggerInvalidException.new("Value [#{name}] should be a float but is [#{value}]")
95
+ end
96
+ end
97
+
98
+ # Validate a numerical value
99
+ # @param value [Numeric] the value
100
+ def validate_numerical_value(value)
101
+ validate_numerical_value_internal(
102
+ value,
103
+ PARAMS_MINIMUM,
104
+ PARAMS_EXCLUSIVE_MINIMUM,
105
+ '>=',
106
+ '>')
107
+ validate_numerical_value_internal(
108
+ value,
109
+ PARAMS_MAXIMUM,
110
+ PARAMS_EXCLUSIVE_MAXIMUM,
111
+ '<=',
112
+ '<')
113
+ end
114
+
115
+ # Validate a date time
116
+ def validate_value_date_time(value)
117
+ begin
118
+ DateTime.rfc3339(value)
119
+ rescue ArgumentError
120
+ raise SwaggerInvalidException.new("Value [#{name}] should be a date time but is [#{value}]")
121
+ end
122
+ end
123
+
124
+ # Validate a string
125
+ def validate_value_string(value)
126
+ if value
127
+ validate_value_string_length(value, PARAMS_MIN_LENGTH, '>=')
128
+ validate_value_string_length(value, PARAMS_MAX_LENGTH, '<=')
129
+ end
130
+ value
131
+ end
132
+
133
+ # Validate the length of a string
134
+ # @param value the value to check
135
+ # @param limit_param_name [Symbol] the param that contain the value to compare to
136
+ # @param limit_param_method [String] the comparison method to call
137
+ def validate_value_string_length(value, limit_param_name, limit_param_method)
138
+ if @params.key? limit_param_name
139
+ target_value = @params[limit_param_name]
140
+ unless value.length.send(limit_param_method, target_value)
141
+ raise SwaggerInvalidException.new("Value [#{name}] length should be #{limit_param_method} than #{target_value} but is #{value.length} for [#{value}]")
142
+ end
143
+ end
144
+ end
145
+
146
+ # Validate the value of a number
147
+ # @param value the value to check
148
+ # @param limit_param_name [Symbol] the param that contain the value to compare to
149
+ # @param exclusive_limit_param_name [Symbol] the param that indicates if the comparison is absolute
150
+ # @param limit_param_method [String] the comparison method to call
151
+ # @param exclusive_limit_param_method [String] the absolute comparison method to call
152
+ def validate_numerical_value_internal(value, limit_param_name, exclusive_limit_param_name, limit_param_method, exclusive_limit_param_method)
153
+ if @params.key? limit_param_name
154
+ target_value = @params[limit_param_name]
155
+ method_to_call = @params[exclusive_limit_param_name] ? exclusive_limit_param_method : limit_param_method
156
+ unless value.send(method_to_call, target_value)
157
+ raise SwaggerInvalidException.new("Value [#{name}] should be #{method_to_call} than [#{target_value}] but is [#{value}]")
158
+ end
159
+ end
160
+ end
161
+
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,69 @@
1
+ require_relative '../swagger-parameter-helper'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ module Processing
8
+
9
+ # Make headers available as a Hash
10
+ class HashForHeaders < Hash
11
+
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def [](name)
17
+ @app.request.env[key_to_header_key(name)]
18
+ end
19
+
20
+ def key?(name)
21
+ @app.request.env.key?(key_to_header_key(name))
22
+ end
23
+
24
+ private
25
+
26
+ # In rack the headers name are transformed
27
+ def key_to_header_key(key)
28
+ "HTTP_#{key.gsub(/-/o, '_').upcase}"
29
+ end
30
+
31
+ end
32
+
33
+ # Dispatch content to a processor
34
+ class SwaggerProcessorDispatcher
35
+
36
+ attr_reader :how_to_pass, :processor
37
+
38
+ include Sinatra::SwaggerExposer::SwaggerParameterHelper
39
+
40
+ # Initialize
41
+ # @param how_to_pass how the value should be passed to the processor
42
+ # @param processor [Sinatra::SwaggerExposer::Processing::SwaggerBaseValueProcessor] processor for the values
43
+ def initialize(how_to_pass, processor)
44
+ @how_to_pass = how_to_pass
45
+ @processor = processor
46
+ end
47
+
48
+ def useful?
49
+ (@how_to_pass != HOW_TO_PASS_PATH) && @processor.useful?
50
+ end
51
+
52
+ # Process the value
53
+ def process(app, parsed_body)
54
+ case @how_to_pass
55
+ when HOW_TO_PASS_PATH
56
+ # can't validate
57
+ when HOW_TO_PASS_QUERY
58
+ @processor.process(app.params)
59
+ when HOW_TO_PASS_HEADER
60
+ @processor.process(HashForHeaders.new(app))
61
+ when HOW_TO_PASS_BODY
62
+ @processor.process(parsed_body || {})
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,128 @@
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
+ HTML_CONTENT_TYPE = MIME::Types['text/html'].first
54
+
55
+ # Run the processor the call the route content
56
+ # @param app the sinatra app being run
57
+ # @param block_params [Array] the block parameters
58
+ # @param block the block containing the route content
59
+ def run(app, block_params, &block)
60
+ parsed_body = {}
61
+ if JSON_CONTENT_TYPE.like?(app.env['CONTENT_TYPE'])
62
+ body = app.request.body.read
63
+ unless body.empty?
64
+ begin
65
+ parsed_body = JSON.parse(body)
66
+ rescue JSON::ParserError => e
67
+ return [400, {:code => 400, :message => e.message}.to_json]
68
+ end
69
+ end
70
+ end
71
+ app.params['parsed_body'] = parsed_body
72
+ unless @processors_dispatchers.empty?
73
+ @processors_dispatchers.each do |processor_dispatcher|
74
+ begin
75
+ processor_dispatcher.process(app, parsed_body)
76
+ rescue SwaggerInvalidException => e
77
+ app.content_type :json
78
+ return [400, {:code => 400, :message => e.message}.to_json]
79
+ end
80
+ end
81
+ end
82
+ if block
83
+ # Execute the block in the context of the app
84
+ app.instance_exec(*block_params, &block)
85
+ else
86
+ ''
87
+ end
88
+ end
89
+
90
+ # Validate the response
91
+ # @param response_body [String] the body
92
+ # @param content_type [String] the content type
93
+ # @param response_status [Integer] the status
94
+ def validate_response(response_body, content_type, response_status)
95
+ validate_response_content_type(content_type, response_status)
96
+ if @response_processors.key?(response_status)
97
+ response_processor = response_processors[response_status]
98
+ if JSON_CONTENT_TYPE.like?(content_type) && response_processor
99
+ response_processor.validate_response(response_body)
100
+ end
101
+ else
102
+ raise SwaggerInvalidException.new("Status with unknown response status [#{response_status}], known statuses are [#{@response_processors.keys.join(', ')}] response value is #{response_body}")
103
+ end
104
+ end
105
+
106
+ # Validate a response content type
107
+ # @param content_type [String] the content type to validate
108
+ # @param response_status [Integer] the status
109
+ def validate_response_content_type(content_type, response_status)
110
+ if content_type.nil? && (response_status == 204)
111
+ # No content and no content type => everything is OK
112
+ elsif @produces
113
+ # if there is no content type Sinatra will default to html so we simulate it here
114
+ if content_type.nil? && @produces_types.any? { |produce| produce.like?(HTML_CONTENT_TYPE) }
115
+ content_type = HTML_CONTENT_TYPE
116
+ end
117
+ unless @produces_types.any? { |produce| produce.like?(content_type) }
118
+ raise SwaggerInvalidException.new("Undeclared content type [#{content_type}], declared content type are [#{@produces.join(', ')}]")
119
+ end
120
+ elsif !JSON_CONTENT_TYPE.like?(content_type)
121
+ raise SwaggerInvalidException.new("Undeclared content type [#{content_type}], if no declaration for the endpoint you should only return json")
122
+ end
123
+ end
124
+
125
+ end
126
+ end
127
+ end
128
+ 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
@@ -0,0 +1,55 @@
1
+ module Sinatra
2
+
3
+ module SwaggerExposer
4
+
5
+ # Create the swagger content
6
+ class SwaggerContentCreator
7
+
8
+ def initialize(swagger_info, swagger_types, swagger_endpoints)
9
+ @swagger_info = swagger_info
10
+ @swagger_types = swagger_types
11
+ @swagger_endpoints = swagger_endpoints
12
+ end
13
+
14
+ def to_swagger
15
+ result = {
16
+ swagger: '2.0',
17
+ consumes: ['application/json'],
18
+ produces: ['application/json'],
19
+ }
20
+ if @swagger_info
21
+ result[:info] = @swagger_info.to_swagger
22
+ end
23
+
24
+ swagger_types_as_swagger = @swagger_types.to_swagger
25
+ if swagger_types_as_swagger
26
+ result[:definitions] = swagger_types_as_swagger
27
+ end
28
+
29
+ unless @swagger_endpoints.empty?
30
+ result_endpoints = {}
31
+
32
+ # swagger need the endpoints to be grouped by path
33
+ endpoints_by_path = @swagger_endpoints.group_by { |endpoint| endpoint.path }
34
+ endpoints_by_path.keys.sort.each do |path|
35
+ endpoints = endpoints_by_path[path]
36
+
37
+ result_endpoints_for_path = {}
38
+ endpoints.each do |endpoint|
39
+ result_endpoints_for_path[endpoint.type] = endpoint.to_swagger
40
+ end
41
+
42
+ result_endpoints[path] = result_endpoints_for_path
43
+
44
+ end
45
+ result[:paths] = result_endpoints
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end