sinatra-swagger-exposer 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa97395699d5708bb599a1793df03d11ee73cc6a
4
- data.tar.gz: 8dcad9fd2667784b9a6ba79362e6109b344779d8
3
+ metadata.gz: 60f345d0ee591c0a428d66ccfb5f5a7f2ae0e9b8
4
+ data.tar.gz: 4b750003265417b68303fa4f0112fbf0cb3f23a0
5
5
  SHA512:
6
- metadata.gz: ea832406300901d1975504d66725fec8669f465756c75abf68fdf8e3a58af5a3a530659acb50887c2ebceccd0b9287daf89cfc4e769a3365659b05bd53d16a76
7
- data.tar.gz: 995af3f7a7e77b2ba82ef9644856d324b9f96cc8acf582f8fdd448f3f69ebcee34a44075ae1c84fd7a066bce44ea2af4fdf59fe681554a129d88e0017fbd6834
6
+ metadata.gz: a2f6f72e7ce27da5ebcd9b6517a81b6a9c6cb5cecbda87639d60fabd1142f58ea748315ea220dd7b4779f6bc8e982bee00af770f2a7f267492653af3fee54162
7
+ data.tar.gz: aadfda04a6b04980d1b875966c6886f4fac0f3a903596b88817742f9038e872fc2b2e2ba853e0c78a11623332befa08d4d4c51894b62a287a5c5f29e553ac177
@@ -1,8 +1,15 @@
1
+ # 0.2.0
2
+
3
+ - Fix empty value returned from validation when a parameter is a String
4
+ - Added maxLength and minLength validations
5
+ - Add produces parameter
6
+ - Add fluent syntax
7
+
1
8
  # 0.1.0
2
9
 
3
10
  - Added verification and enrichment form params
4
11
  - Added type extends
5
- - Added validation for min and max value
12
+ - Added validation for min and max value for numbers
6
13
 
7
14
  # 0.0.1
8
15
 
data/README.md CHANGED
@@ -65,6 +65,24 @@ end
65
65
 
66
66
  The swagger json endpoint will be exposed at `/swagger_doc.json`.
67
67
 
68
+ You can also use a more fluent variant by providing a hash to the `endpoint` method
69
+
70
+
71
+ ```ruby
72
+ endpoint :description => 'Base method to ping',
73
+ :responses { 200 => ['Status', 'Standard response']}
74
+ :tags 'Ping'
75
+ get '/' do
76
+ json({'status' => 'OK'})
77
+ end
78
+ ```
79
+
80
+ The hash should contains the key `description`, `summary`, `path`, `tags`, `responses` and `params`.
81
+ Note both `responses` and `params` takes a hash as argument `hash(param_name =>param_details)` and `hash(status_code=>res_param)`
82
+
83
+ If the equivalent methods have more than one param, theses are wrapped in an array.
84
+
85
+
68
86
  ## Detailed example
69
87
 
70
88
  A more complete example is available [here](https://github.com/archiloque/sinatra-swagger-exposer/tree/master/example).
@@ -1,5 +1,7 @@
1
1
  require_relative 'swagger-invalid-exception'
2
+ require_relative 'swagger-parameter-helper'
2
3
  require_relative 'swagger-parameter-preprocessor'
4
+ require_relative 'swagger-type-property'
3
5
  require_relative 'swagger-utilities'
4
6
 
5
7
  module Sinatra
@@ -9,35 +11,7 @@ module Sinatra
9
11
  class SwaggerEndpointParameter
10
12
 
11
13
  include SwaggerUtilities
12
-
13
- HOW_TO_PASS_BODY = 'body'
14
- HOW_TO_PASS_HEADER = 'header'
15
- HOW_TO_PASS_PATH = 'path'
16
- HOW_TO_PASS_QUERY = 'query'
17
- HOW_TO_PASS = [HOW_TO_PASS_PATH, HOW_TO_PASS_QUERY, HOW_TO_PASS_HEADER, 'formData', HOW_TO_PASS_BODY]
18
-
19
- TYPE_INTEGER = 'integer'
20
- TYPE_BOOLEAN = 'boolean'
21
- TYPE_NUMBER = 'number'
22
- TYPE_STRING = 'string'
23
- PRIMITIVE_TYPES_FOR_NON_BODY = [TYPE_STRING, TYPE_NUMBER, TYPE_INTEGER, TYPE_BOOLEAN]
24
-
25
- PARAMS_FORMAT = :format
26
- PARAMS_DEFAULT = :default
27
- PARAMS_EXAMPLE = :example
28
- PARAMS_MAXIMUM = :maximum
29
- PARAMS_MINIMUM = :minimum
30
- PARAMS_EXCLUSIVE_MINIMUM = :exclusiveMinimum
31
- PARAMS_EXCLUSIVE_MAXIMUM = :exclusiveMaximum
32
- PARAMS_LIST = [
33
- PARAMS_FORMAT,
34
- PARAMS_DEFAULT,
35
- PARAMS_EXAMPLE,
36
- PARAMS_MAXIMUM,
37
- PARAMS_MINIMUM,
38
- PARAMS_EXCLUSIVE_MINIMUM,
39
- PARAMS_EXCLUSIVE_MAXIMUM,
40
- ]
14
+ include SwaggerParameterHelper
41
15
 
42
16
  def initialize(name, description, how_to_pass, required, type, params, known_types)
43
17
  unless name.is_a?(String) || name.is_a?(Symbol)
@@ -70,7 +44,7 @@ module Sinatra
70
44
  end
71
45
  @required = required
72
46
 
73
- white_list_params(params, PARAMS_LIST)
47
+ params = white_list_params(params, PARAMS_LIST, SwaggerTypeProperty::PROPERTIES)
74
48
  validate_params(params)
75
49
  @params = params
76
50
  end
@@ -78,14 +52,18 @@ module Sinatra
78
52
  # Validate parameters
79
53
  # @param params [Hash]
80
54
  def validate_params(params)
81
- validate_limit_parameter(params, PARAMS_MAXIMUM, PARAMS_EXCLUSIVE_MAXIMUM)
82
- validate_limit_parameter(params, PARAMS_MINIMUM, PARAMS_EXCLUSIVE_MINIMUM)
55
+ validate_limit_parameters(params)
56
+ validate_length_parameters(params)
83
57
  end
84
58
 
59
+ # Create the corresponding SwaggerParameterPreprocessor
60
+ # @return [Sinatra::SwaggerExposer::SwaggerParameterPreprocessor]
85
61
  def preprocessor
86
62
  SwaggerParameterPreprocessor.new(@name, @how_to_pass, @required, @type, @params[:default], @params)
87
63
  end
88
64
 
65
+ # Return the swagger version
66
+ # @return [Hash]
89
67
  def to_swagger
90
68
  result = {
91
69
  :name => @name,
@@ -138,7 +116,7 @@ module Sinatra
138
116
 
139
117
  # Test if a parameter is a boolean
140
118
  # @param name the parameter's name
141
- # @value value the parameter's value
119
+ # @param value value the parameter's value
142
120
  # @return [NilClass]
143
121
  def check_boolean(name, value)
144
122
  unless [true, false].include? value
@@ -146,23 +124,71 @@ module Sinatra
146
124
  end
147
125
  end
148
126
 
127
+ # Validate the limit parameters
128
+ # @param params [Hash] the parameters
129
+ def validate_limit_parameters(params)
130
+ max = validate_limit_parameter(params, PARAMS_MAXIMUM, PARAMS_EXCLUSIVE_MAXIMUM)
131
+ min = validate_limit_parameter(params, PARAMS_MINIMUM, PARAMS_EXCLUSIVE_MINIMUM)
132
+ if min && max && (max < min)
133
+ raise SwaggerInvalidException.new("Minimum value [#{min}] can't be more than maximum value [#{max}]")
134
+ end
135
+ end
136
+
149
137
  # Validate a limit param like maximum and exclusiveMaximum
150
138
  # @param params [Hash] the parameters
151
139
  # @param limit_param_name [Symbol] the limit parameter name
152
140
  # @param exclusive_limit_param_name [Symbol] the exclusive limit parameter name
153
141
  def validate_limit_parameter(params, limit_param_name, exclusive_limit_param_name)
142
+ parameter_value = nil
154
143
  if params.key? limit_param_name
155
144
  unless [TYPE_INTEGER, TYPE_NUMBER].include? @type
156
- raise SwaggerInvalidException.new("Parameter #{limit_param_name} can only be specified for type #{TYPE_INTEGER} and #{TYPE_NUMBER} and not for [#{@type}]")
145
+ raise SwaggerInvalidException.new("Parameter #{limit_param_name} can only be specified for types #{TYPE_INTEGER} or #{TYPE_NUMBER} and not for [#{@type}]")
146
+ end
147
+ parameter_value = params[limit_param_name]
148
+ unless parameter_value.is_a? Numeric
149
+ raise SwaggerInvalidException.new("Parameter #{limit_param_name} must be a numeric and can not be [#{parameter_value}]")
157
150
  end
158
151
  end
159
152
 
160
153
  if params.key? exclusive_limit_param_name
161
- check_boolean(PARAMS_EXCLUSIVE_MINIMUM, params[exclusive_limit_param_name])
154
+ check_boolean(exclusive_limit_param_name, params[exclusive_limit_param_name])
162
155
  unless params.key? limit_param_name
163
156
  raise SwaggerInvalidException.new("You can't have a #{exclusive_limit_param_name} value without a #{limit_param_name}")
164
157
  end
165
158
  end
159
+ parameter_value
160
+ end
161
+
162
+ # Validate the length parameters minLength and maxLength
163
+ # @param params [Hash] the parameters
164
+ def validate_length_parameters(params)
165
+ min_length = validate_length_parameter(params, PARAMS_MIN_LENGTH)
166
+ max_length = validate_length_parameter(params, PARAMS_MAX_LENGTH)
167
+
168
+ if min_length && max_length && (max_length < min_length)
169
+ raise SwaggerInvalidException.new("Minimum length #{min_length} can't be more than maximum length #{max_length}")
170
+ end
171
+ end
172
+
173
+ # Validate a length param like minLength and maxLength
174
+ # @param params [Hash] the parameters
175
+ # @param length_param_name [Symbol] the length parameter name
176
+ # @return [Integer] the parameter value if it is present
177
+ def validate_length_parameter(params, length_param_name)
178
+ if params.key? length_param_name
179
+ if @type == TYPE_STRING
180
+ parameter_value = params[length_param_name]
181
+ unless parameter_value.is_a? Integer
182
+ raise SwaggerInvalidException.new("Parameter #{length_param_name} must be an integer and can not be [#{parameter_value}]")
183
+ end
184
+ parameter_value
185
+ else
186
+ raise SwaggerInvalidException.new("Parameter #{length_param_name} can only be specified for type #{TYPE_STRING} and not for [#{@type}]")
187
+ end
188
+
189
+ else
190
+ nil
191
+ end
166
192
  end
167
193
 
168
194
  end
@@ -9,8 +9,10 @@ module Sinatra
9
9
 
10
10
  include SwaggerUtilities
11
11
 
12
+ RESPONSE_PRIMITIVES_FILES = PRIMITIVE_TYPES + [TYPE_FILE]
13
+
12
14
  def initialize(type, description, known_types)
13
- get_type(type, known_types + PRIMITIVE_TYPES)
15
+ get_type(type, known_types + RESPONSE_PRIMITIVES_FILES)
14
16
  if description
15
17
  @description = description
16
18
  end
@@ -23,7 +25,7 @@ module Sinatra
23
25
  if @type == 'array'
24
26
  schema = {:type => 'array'}
25
27
  if @items
26
- if PRIMITIVE_TYPES.include? @items
28
+ if RESPONSE_PRIMITIVES_FILES.include? @items
27
29
  schema[:items] = {:type => @items}
28
30
  else
29
31
  schema[:items] = ref_to_type(@items)
@@ -31,7 +33,7 @@ module Sinatra
31
33
  end
32
34
  result[:schema] = schema
33
35
  else
34
- if PRIMITIVE_TYPES.include? @type
36
+ if RESPONSE_PRIMITIVES_FILES.include? @type
35
37
  result[:schema] = {:type => @type}
36
38
  else
37
39
  result[:schema] = ref_to_type(@type)
@@ -12,9 +12,9 @@ module Sinatra
12
12
 
13
13
  attr_reader :path, :type, :request_preprocessor
14
14
 
15
- def initialize(type, path, parameters, responses, summary, description, tags)
15
+ def initialize(type, sinatra_path, parameters, responses, summary, description, tags, explicit_path, produces)
16
16
  @type = type
17
- @path = sinatra_path_to_swagger_path(path)
17
+ @path = swagger_path(sinatra_path, explicit_path)
18
18
  @request_preprocessor = SwaggerRequestPreprocessor.new
19
19
 
20
20
  @parameters = parameters
@@ -37,12 +37,13 @@ module Sinatra
37
37
  if tags
38
38
  @attributes[:tags] = tags
39
39
  end
40
+ if produces
41
+ @attributes[:produces] = produces
42
+ end
40
43
  end
41
44
 
42
45
  def to_swagger
43
- result = {
44
- produces: ['application/json'],
45
- }.merge(@attributes)
46
+ result = @attributes.clone
46
47
 
47
48
  unless @parameters.empty?
48
49
  result[:parameters] = @parameters.collect { |parameter| parameter.to_swagger }
@@ -58,14 +59,23 @@ module Sinatra
58
59
  REGEX_PATH_PARAM_MIDDLE = /\A(.*\/)\:([a-z]+)\/(.+)\z/
59
60
  REGEX_PATH_PARAM_END = /\A(.*)\/:([a-z]+)\z/
60
61
 
61
- def sinatra_path_to_swagger_path(path)
62
- while (m = REGEX_PATH_PARAM_MIDDLE.match(path))
63
- path = "#{m[1]}{#{m[2]}}/#{m[3]}"
64
- end
65
- if (m = REGEX_PATH_PARAM_END.match(path))
66
- path = "#{m[1]}/{#{m[2]}}"
62
+ # Get the endpoint swagger path
63
+ # @param sinatra_path the path declared in the sinatra app
64
+ # @param explicit_path an explicit path the user can specify
65
+ def swagger_path(sinatra_path, explicit_path)
66
+ if explicit_path
67
+ explicit_path
68
+ elsif sinatra_path.is_a? String
69
+ while (m = REGEX_PATH_PARAM_MIDDLE.match(sinatra_path))
70
+ sinatra_path = "#{m[1]}{#{m[2]}}/#{m[3]}"
71
+ end
72
+ if (m = REGEX_PATH_PARAM_END.match(sinatra_path))
73
+ sinatra_path = "#{m[1]}/{#{m[2]}}"
74
+ end
75
+ sinatra_path
76
+ else
77
+ raise SwaggerInvalidException.new("You need to specify a path when using a non-string path [#{sinatra_path}]")
67
78
  end
68
- path
69
79
  end
70
80
 
71
81
  def to_s
@@ -49,6 +49,11 @@ module Sinatra
49
49
  set_if_type_and_not_exist(summary, :summary, String)
50
50
  end
51
51
 
52
+ # Provide a path
53
+ def endpoint_path(path)
54
+ set_if_type_and_not_exist(path, :path, String)
55
+ end
56
+
52
57
  # Provide a description for the endpoint
53
58
  def endpoint_description(description)
54
59
  set_if_type_and_not_exist(description, :description, String)
@@ -56,7 +61,12 @@ module Sinatra
56
61
 
57
62
  # Provide tags for the endpoint
58
63
  def endpoint_tags(*tags)
59
- set_if_type_and_not_exist(tags, :tags, nil)
64
+ set_if_not_exist(tags, :tags)
65
+ end
66
+
67
+ # Provide produces params for the endpoint
68
+ def endpoint_produces(*produces)
69
+ set_if_not_exist(produces, :produces)
60
70
  end
61
71
 
62
72
  # Define parameter for the endpoint
@@ -73,6 +83,35 @@ module Sinatra
73
83
  settings.swagger_types.keys)
74
84
  end
75
85
 
86
+ # Define fluent endpoint dispatcher
87
+ # @param params [Hash] the parameters
88
+ def endpoint(params)
89
+ params.each_pair do |param_name, param_value|
90
+ case param_name
91
+ when :summary
92
+ endpoint_summary param_value
93
+ when :description
94
+ endpoint_description param_value
95
+ when :tags
96
+ endpoint_tags *param_value
97
+ when :produces
98
+ endpoint_produces *param_value
99
+ when :path
100
+ endpoint_path param_value
101
+ when :parameters
102
+ param_value.each do |param, args_param|
103
+ endpoint_parameter param, *args_param
104
+ end
105
+ when :responses
106
+ param_value.each do |code, args_response|
107
+ endpoint_response code, *args_response
108
+ end
109
+ else
110
+ raise SwaggerInvalidException.new("Invalid endpoint parameter [#{param_name}]")
111
+ end
112
+ end
113
+ end
114
+
76
115
  # General information
77
116
  def general_info(params)
78
117
  set :swagger_info, SwaggerInfo.new(params)
@@ -99,8 +138,8 @@ module Sinatra
99
138
  super(verb, path, options, &block)
100
139
  else
101
140
  request_preprocessor = process_endpoint(verb.downcase, path, options)
102
- super(verb, path, options) do
103
- request_preprocessor.run(self, &block)
141
+ super(verb, path, options) do |*params|
142
+ request_preprocessor.run(self, params, &block)
104
143
  end
105
144
  end
106
145
  end
@@ -120,7 +159,9 @@ module Sinatra
120
159
  current_endpoint_responses.clone,
121
160
  current_endpoint_info[:summary],
122
161
  current_endpoint_info[:description],
123
- current_endpoint_info[:tags])
162
+ current_endpoint_info[:tags],
163
+ current_endpoint_info[:path],
164
+ current_endpoint_info[:produces])
124
165
  settings.swagger_endpoints << endpoint
125
166
  current_endpoint_info.clear
126
167
  current_endpoint_parameters.clear
@@ -128,21 +169,23 @@ module Sinatra
128
169
  endpoint.request_preprocessor
129
170
  end
130
171
 
131
- def set_if_type_and_not_exist(value, name, type)
132
- if type
133
- unless value.is_a? type
134
- raise SwaggerInvalidException.new("#{name} [#{value}] should be a #{type.to_s.downcase}")
135
- end
136
- end
172
+ def set_if_not_exist(value, name)
137
173
  if settings.swagger_current_endpoint_info.key? name
138
- raise SwaggerInvalidException.new("#{name} with value [#{value}] already defined: #{settings.swagger_current_endpoint_info[name]}")
174
+ raise SwaggerInvalidException.new("#{name} with value [#{value}] already defined: [#{settings.swagger_current_endpoint_info[name]}]")
139
175
  end
140
176
  settings.swagger_current_endpoint_info[name] = value
141
177
  end
142
178
 
179
+ def set_if_type_and_not_exist(value, name, type)
180
+ unless value.is_a? type
181
+ raise SwaggerInvalidException.new("#{name} [#{value}] should be a #{type.to_s.downcase}")
182
+ end
183
+ set_if_not_exist(value, name)
184
+ end
185
+
143
186
  def check_if_not_duplicate(key, values, name)
144
187
  if values.key? key
145
- raise SwaggerInvalidException.new("#{name} already exist for #{key} with value #{values[key]}")
188
+ raise SwaggerInvalidException.new("#{name} already exist for #{key} with value [#{values[key]}]")
146
189
  end
147
190
  end
148
191
 
@@ -0,0 +1,73 @@
1
+ module Sinatra
2
+
3
+ module SwaggerExposer
4
+
5
+ # Helper for handling the parameters
6
+ module SwaggerParameterHelper
7
+
8
+ HOW_TO_PASS_BODY = 'body'
9
+ HOW_TO_PASS_HEADER = 'header'
10
+ HOW_TO_PASS_PATH = 'path'
11
+ HOW_TO_PASS_QUERY = 'query'
12
+ HOW_TO_PASS = [HOW_TO_PASS_PATH, HOW_TO_PASS_QUERY, HOW_TO_PASS_HEADER, 'formData', HOW_TO_PASS_BODY]
13
+
14
+ TYPE_BOOLEAN = 'boolean'
15
+ TYPE_BYTE = 'byte'
16
+ TYPE_DATE = 'date'
17
+ TYPE_DOUBLE = 'double'
18
+ TYPE_DATE_TIME = 'dateTime'
19
+ TYPE_FLOAT = 'float'
20
+ TYPE_INTEGER = 'integer'
21
+ TYPE_LONG = 'long'
22
+ TYPE_NUMBER = 'number'
23
+ TYPE_PASSWORD = 'password'
24
+ TYPE_STRING = 'string'
25
+
26
+ PRIMITIVE_TYPES = [
27
+ TYPE_INTEGER,
28
+ TYPE_LONG,
29
+ TYPE_FLOAT,
30
+ TYPE_DOUBLE,
31
+ TYPE_STRING,
32
+ TYPE_BYTE,
33
+ TYPE_BOOLEAN,
34
+ TYPE_DATE,
35
+ TYPE_DATE_TIME,
36
+ TYPE_PASSWORD,
37
+ ]
38
+
39
+ TYPE_FILE = 'file'
40
+
41
+ PRIMITIVE_TYPES_FOR_NON_BODY = [TYPE_STRING, TYPE_NUMBER, TYPE_INTEGER, TYPE_BOOLEAN]
42
+
43
+ PARAMS_FORMAT = :format
44
+ PARAMS_DEFAULT = :default
45
+ PARAMS_EXAMPLE = :example
46
+
47
+ # For numbers
48
+ PARAMS_MINIMUM = :minimum
49
+ PARAMS_MAXIMUM = :maximum
50
+ PARAMS_EXCLUSIVE_MINIMUM = :exclusiveMinimum
51
+ PARAMS_EXCLUSIVE_MAXIMUM = :exclusiveMaximum
52
+
53
+ # For strings
54
+ PARAMS_MIN_LENGTH = :minLength
55
+ PARAMS_MAX_LENGTH = :maxLength
56
+
57
+ PARAMS_LIST = [
58
+ PARAMS_FORMAT,
59
+ PARAMS_DEFAULT,
60
+ PARAMS_EXAMPLE,
61
+ PARAMS_MINIMUM,
62
+ PARAMS_MAXIMUM,
63
+ PARAMS_EXCLUSIVE_MINIMUM,
64
+ PARAMS_EXCLUSIVE_MAXIMUM,
65
+ PARAMS_MIN_LENGTH,
66
+ PARAMS_MAX_LENGTH,
67
+ ]
68
+
69
+
70
+ end
71
+
72
+ end
73
+ end
@@ -1,4 +1,7 @@
1
+ require 'date'
2
+
1
3
  require_relative 'swagger-endpoint-parameter'
4
+ require_relative 'swagger-parameter-helper'
2
5
  require_relative 'swagger-invalid-exception'
3
6
 
4
7
  module Sinatra
@@ -8,6 +11,8 @@ module Sinatra
8
11
  # Process the parameters for validation and enrichment
9
12
  class SwaggerParameterPreprocessor
10
13
 
14
+ include SwaggerParameterHelper
15
+
11
16
  def initialize(name, how_to_pass, required, type, default, params)
12
17
  @name = name.to_s
13
18
  @how_to_pass = how_to_pass
@@ -17,22 +22,27 @@ module Sinatra
17
22
  @params = params
18
23
 
19
24
  # All headers are upcased
20
- if how_to_pass == SwaggerEndpointParameter::HOW_TO_PASS_HEADER
25
+ if how_to_pass == HOW_TO_PASS_HEADER
21
26
  @name.upcase!
22
27
  end
23
28
  end
24
29
 
25
30
  def useful?
26
- @required || (!@default.nil?) || [SwaggerEndpointParameter::TYPE_NUMBER, SwaggerEndpointParameter::TYPE_INTEGER, SwaggerEndpointParameter::TYPE_BOOLEAN].include?(@type)
31
+ @required ||
32
+ (!@default.nil?) ||
33
+ [TYPE_NUMBER, TYPE_INTEGER, TYPE_BOOLEAN, TYPE_DATE_TIME].include?(@type) || # Must check type
34
+ (@params.key? PARAMS_MIN_LENGTH) || (@params.key? PARAMS_MAX_LENGTH) # Must check string
27
35
  end
28
36
 
29
37
  def run(app, parsed_body)
30
38
  case @how_to_pass
31
- when SwaggerEndpointParameter::HOW_TO_PASS_QUERY, SwaggerEndpointParameter::HOW_TO_PASS_PATH
39
+ when HOW_TO_PASS_PATH
40
+ # can't validate
41
+ when HOW_TO_PASS_QUERY
32
42
  check_param(app.params)
33
- when SwaggerEndpointParameter::HOW_TO_PASS_HEADER
43
+ when HOW_TO_PASS_HEADER
34
44
  check_param(app.headers)
35
- when SwaggerEndpointParameter::HOW_TO_PASS_BODY
45
+ when HOW_TO_PASS_BODY
36
46
  check_param(parsed_body || {})
37
47
  end
38
48
  end
@@ -50,15 +60,20 @@ module Sinatra
50
60
 
51
61
  def validate_param_value(value)
52
62
  case @type
53
- when SwaggerEndpointParameter::TYPE_NUMBER
63
+ when TYPE_NUMBER
54
64
  return validate_param_value_number(value)
55
- when SwaggerEndpointParameter::TYPE_INTEGER
65
+ when TYPE_INTEGER
56
66
  return validate_param_value_integer(value)
57
- when SwaggerEndpointParameter::TYPE_BOOLEAN
67
+ when TYPE_BOOLEAN
58
68
  return validate_param_value_boolean(value)
69
+ when TYPE_DATE_TIME
70
+ return validate_param_value_date_time(value)
71
+ else
72
+ return validate_param_value_string(value)
59
73
  end
60
74
  end
61
75
 
76
+ # Validate a boolean parameter
62
77
  def validate_param_value_boolean(value)
63
78
  if (value == 'true') || value.is_a?(TrueClass)
64
79
  return true
@@ -69,6 +84,7 @@ module Sinatra
69
84
  end
70
85
  end
71
86
 
87
+ # Validate an integer parameter
72
88
  def validate_param_value_integer(value)
73
89
  begin
74
90
  f = Float(value)
@@ -88,6 +104,7 @@ module Sinatra
88
104
  end
89
105
  end
90
106
 
107
+ # Validate a number parameter
91
108
  def validate_param_value_number(value)
92
109
  begin
93
110
  value = Float(value)
@@ -105,24 +122,55 @@ module Sinatra
105
122
  def validate_numerical_value(value)
106
123
  validate_numerical_value_internal(
107
124
  value,
108
- SwaggerEndpointParameter::PARAMS_MINIMUM,
109
- SwaggerEndpointParameter::PARAMS_EXCLUSIVE_MINIMUM,
125
+ PARAMS_MINIMUM,
126
+ PARAMS_EXCLUSIVE_MINIMUM,
110
127
  '>=',
111
128
  '>')
112
129
  validate_numerical_value_internal(
113
130
  value,
114
- SwaggerEndpointParameter::PARAMS_MAXIMUM,
115
- SwaggerEndpointParameter::PARAMS_EXCLUSIVE_MAXIMUM,
131
+ PARAMS_MAXIMUM,
132
+ PARAMS_EXCLUSIVE_MAXIMUM,
116
133
  '<=',
117
134
  '<')
118
135
  end
119
136
 
137
+ # Validate a date time parameter
138
+ def validate_param_value_date_time(value)
139
+ begin
140
+ DateTime.rfc3339(value)
141
+ rescue ArgumentError
142
+ raise SwaggerInvalidException.new("Parameter [#{@name}] should be a date time but is [#{value}]")
143
+ end
144
+ end
145
+
146
+ # Validate a string parameter
147
+ def validate_param_value_string(value)
148
+ if value
149
+ validate_param_value_string_length(value, PARAMS_MIN_LENGTH, '>=')
150
+ validate_param_value_string_length(value, PARAMS_MAX_LENGTH, '<=')
151
+ end
152
+ value
153
+ end
154
+
155
+ # Validate the length of a string parameter
156
+ # @param value the value to check
157
+ # @param limit_param_name [Symbol] the param that contain the value to compare to
158
+ # @param limit_param_method [String] the comparison method to call
159
+ def validate_param_value_string_length(value, limit_param_name, limit_param_method)
160
+ if @params.key? limit_param_name
161
+ target_value = @params[limit_param_name]
162
+ unless value.length.send(limit_param_method, target_value)
163
+ raise SwaggerInvalidException.new("Parameter [#{@name}] length should be #{limit_param_method} than #{target_value} but is #{value.length} for [#{value}]")
164
+ end
165
+ end
166
+ end
167
+
120
168
  # Validate the value of a number
121
- # @params value the value to check
122
- # @params limit_param_name [Symbol] the param that contain the value to compare to
123
- # @params exclusive_limit_param_name [Symbol] the param that indicates if the comparison is absolute
124
- # @params limit_param_method [String] the comparison method to call
125
- # @params exclusive_limit_param_method [String] the absolute comparison method to call
169
+ # @param value the value to check
170
+ # @param limit_param_name [Symbol] the param that contain the value to compare to
171
+ # @param exclusive_limit_param_name [Symbol] the param that indicates if the comparison is absolute
172
+ # @param limit_param_method [String] the comparison method to call
173
+ # @param exclusive_limit_param_method [String] the absolute comparison method to call
126
174
  def validate_numerical_value_internal(value, limit_param_name, exclusive_limit_param_name, limit_param_method, exclusive_limit_param_method)
127
175
  if @params.key? limit_param_name
128
176
  target_value = @params[limit_param_name]
@@ -19,10 +19,14 @@ module Sinatra
19
19
  @preprocessors << preprocessor
20
20
  end
21
21
 
22
- def run(app, &block)
22
+ # Run the preprocessor the call the route content
23
+ # @param app the sinatra app being run
24
+ # @params block_params [Array] the block parameters
25
+ # @param block the block containing the route content
26
+ def run(app, block_params, &block)
23
27
  parsed_body = {}
24
28
  if app.env['CONTENT_TYPE'] == 'application/json'
25
- body = app.request.body
29
+ body = app.request.body.read
26
30
  unless body.empty?
27
31
  parsed_body = JSON.parse(body)
28
32
  end
@@ -40,7 +44,7 @@ module Sinatra
40
44
  end
41
45
  if block
42
46
  # Execute the block in the context of the app
43
- app.instance_eval(&block)
47
+ app.instance_exec(*block_params, &block)
44
48
  else
45
49
  ''
46
50
  end
@@ -10,7 +10,7 @@ module Sinatra
10
10
 
11
11
  include SwaggerUtilities
12
12
 
13
- OTHER_PROPERTIES = [:example, :description, :format]
13
+ OTHER_PROPERTIES = [:example, :description, :format, :minLength, :maxLength]
14
14
  PROPERTIES = [:type] + OTHER_PROPERTIES
15
15
 
16
16
  def initialize(type_name, property_name, property_properties, known_types)
@@ -1,4 +1,5 @@
1
1
  require_relative 'swagger-invalid-exception'
2
+ require_relative 'swagger-parameter-helper'
2
3
 
3
4
  module Sinatra
4
5
 
@@ -6,18 +7,7 @@ module Sinatra
6
7
 
7
8
  module SwaggerUtilities
8
9
 
9
- PRIMITIVE_TYPES = [
10
- 'integer',
11
- 'long',
12
- 'float',
13
- 'double',
14
- 'string',
15
- 'byte',
16
- 'boolean',
17
- 'date',
18
- 'dateTime',
19
- 'password',
20
- ]
10
+ include ::Sinatra::SwaggerExposer::SwaggerParameterHelper
21
11
 
22
12
  def ref_to_type(type)
23
13
  {'$ref' => "#/definitions/#{type}"}
@@ -35,7 +25,9 @@ module Sinatra
35
25
  # @return [String]
36
26
  def type_to_s(value)
37
27
  if [TrueClass, FalseClass].include? value
38
- 'boolean'
28
+ TYPE_BOOLEAN
29
+ elsif value == DateTime
30
+ TYPE_DATE_TIME
39
31
  elsif value.is_a? Class
40
32
  value.to_s.downcase
41
33
  else
@@ -60,15 +52,20 @@ module Sinatra
60
52
  end
61
53
 
62
54
  # Validate if a parameter is in a list of available values
63
- # @param params the parameter
55
+ # @param params [Hash] the parameters
64
56
  # @param allowed_values [Enumerable, #include?] the allowed values
65
- # @return [NilClass]
66
- def white_list_params(params, allowed_values)
57
+ # @param ignored_values [Enumerable, #include?] values to ignore
58
+ # @return [Hash] the filtered hash
59
+ def white_list_params(params, allowed_values, ignored_values = [])
60
+ result = {}
67
61
  params.each_pair do |key, value|
68
- unless allowed_values.include? key
62
+ if allowed_values.include? key
63
+ result[key] = value
64
+ elsif !ignored_values.include?(key)
69
65
  raise SwaggerInvalidException.new("Unknown property [#{key}] with value [#{value}]#{list_or_none(allowed_values, 'properties')}")
70
66
  end
71
67
  end
68
+ result
72
69
  end
73
70
 
74
71
  def list_or_none(list, name)
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module SwaggerExposer
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -3,6 +3,8 @@ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'sinatra/swagger-exposer/version'
5
5
 
6
+ excluded_patterns = ['test/', 'example/', '.travis.yml', '.gitignore']
7
+
6
8
  Gem::Specification.new do |spec|
7
9
  spec.name = 'sinatra-swagger-exposer'
8
10
  spec.version = Sinatra::SwaggerExposer::VERSION
@@ -13,7 +15,7 @@ Gem::Specification.new do |spec|
13
15
  spec.homepage = 'https://github.com/archiloque/sinatra-swagger-exposer'
14
16
  spec.license = 'MIT'
15
17
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| excluded_patterns.any?{|ep| f.start_with?(ep)}}
17
19
  spec.require_paths = ['lib']
18
20
 
19
21
  spec.add_dependency 'sinatra', '~> 1.4'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-swagger-exposer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Kirch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-11 00:00:00.000000000 Z
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -101,18 +101,12 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - ".gitignore"
105
- - ".travis.yml"
106
104
  - CHANGELOG.md
107
105
  - CODE_OF_CONDUCT.md
108
106
  - Gemfile
109
107
  - LICENSE.txt
110
108
  - README.md
111
109
  - Rakefile
112
- - example/Gemfile
113
- - example/Gemfile.lock
114
- - example/config.ru
115
- - example/petstore.rb
116
110
  - lib/sinatra/swagger-exposer/swagger-content-creator.rb
117
111
  - lib/sinatra/swagger-exposer/swagger-endpoint-parameter.rb
118
112
  - lib/sinatra/swagger-exposer/swagger-endpoint-response.rb
@@ -120,6 +114,7 @@ files:
120
114
  - lib/sinatra/swagger-exposer/swagger-exposer.rb
121
115
  - lib/sinatra/swagger-exposer/swagger-info.rb
122
116
  - lib/sinatra/swagger-exposer/swagger-invalid-exception.rb
117
+ - lib/sinatra/swagger-exposer/swagger-parameter-helper.rb
123
118
  - lib/sinatra/swagger-exposer/swagger-parameter-preprocessor.rb
124
119
  - lib/sinatra/swagger-exposer/swagger-request-preprocessor.rb
125
120
  - lib/sinatra/swagger-exposer/swagger-type-property.rb
@@ -152,3 +147,4 @@ signing_key:
152
147
  specification_version: 4
153
148
  summary: Expose swagger API from your Sinatra app
154
149
  test_files: []
150
+ has_rdoc:
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- sinatra-swagger-exposer-*.gem
@@ -1,6 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.0
4
- addons:
5
- code_climate:
6
- repo_token: 9327b6a5a4de354ce0da555d4ae4ed262ee0fa2e4d210d8fb82511eee5a2dc2f
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'sinatra', '~> 1.4.5'
4
- gem 'sinatra-cross_origin', '~> 0.3.2'
@@ -1,19 +0,0 @@
1
- GEM
2
- remote: https://rubygems.org/
3
- specs:
4
- rack (1.6.0)
5
- rack-protection (1.5.3)
6
- rack
7
- sinatra (1.4.6)
8
- rack (~> 1.4)
9
- rack-protection (~> 1.4)
10
- tilt (>= 1.3, < 3)
11
- sinatra-cross_origin (0.3.2)
12
- tilt (2.0.1)
13
-
14
- PLATFORMS
15
- ruby
16
-
17
- DEPENDENCIES
18
- sinatra (~> 1.4.5)
19
- sinatra-cross_origin (~> 0.3.2)
@@ -1,2 +0,0 @@
1
- require_relative 'petstore'
2
- run Petstore
@@ -1,145 +0,0 @@
1
- require 'sinatra/base'
2
- require 'json'
3
- require 'sinatra/cross_origin'
4
-
5
- require_relative '../lib/sinatra/swagger-exposer/swagger-exposer'
6
-
7
- class Petstore < Sinatra::Base
8
-
9
- set :logging, true
10
-
11
- register Sinatra::CrossOrigin
12
- set :allow_origin, :any
13
- enable :cross_origin
14
-
15
- register Sinatra::SwaggerExposer
16
-
17
- general_info(
18
- {
19
- :version => '1.0.0',
20
- :title => 'Swagger Petstore',
21
- :description => 'A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification',
22
- :termsOfService => 'http://swagger.io/terms/',
23
- :contact => {:name => 'Swagger API Team',
24
- :email => 'apiteam@swagger.io',
25
- :url => 'http://swagger.io'
26
- },
27
- :license => {
28
- :name => 'Apache 2.0',
29
- :url => 'http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT'
30
- }
31
- }
32
- )
33
-
34
- type 'Error', {
35
- :required => [:code, :message],
36
- :properties => {
37
- :code => {
38
- :type => Integer,
39
- :example => 404,
40
- :description => 'The error code',
41
- },
42
- :message => {
43
- :type => String,
44
- :example => 'Pet not found',
45
- :description => 'The error message',
46
- },
47
- },
48
- }
49
-
50
-
51
- type 'Pet', {
52
- :required => [:id, :name],
53
- :properties => {
54
- :id => {
55
- :type => Integer,
56
- :format => 'int64',
57
- },
58
- :name => {
59
- :type => String,
60
- :example => 'doggie',
61
- :description => 'The pet name',
62
- },
63
- :photoUrls => {
64
- :type => [String],
65
- },
66
- :tags => {
67
- :type => [String],
68
- :description => 'The pet\'s tags',
69
- },
70
- :status => {
71
- :type => String,
72
- :description => 'pet status in the store',
73
- :example => 'sleepy',
74
- },
75
- },
76
- }
77
- type 'Cat', {
78
- # Not yet supported in swagger-ui, see https://github.com/swagger-api/swagger-js/issues/188
79
- :extends => 'Pet',
80
- :properties => {
81
- :fluffy => {
82
- :type => TrueClass,
83
- :description => 'is this cat fluffy ?',
84
- :example => true,
85
- },
86
- },
87
- }
88
-
89
- endpoint_summary 'Finds all the pets'
90
- endpoint_description 'Returns all pets from the system that the user has access to'
91
- endpoint_tags 'Pets'
92
- endpoint_response 200, ['Pet'], 'Standard response'
93
- endpoint_parameter :size, 'The number of pets to return', :query, false, Integer,
94
- {
95
- :example => 100,
96
- :default => 20, # If the caller send no value the default value will be set in the params
97
- :maximum => 100,
98
- :minimum => 0,
99
- :exclusiveMinimum => true,
100
- }
101
- get '/pets' do
102
- content_type :json
103
- [].to_json
104
- end
105
-
106
- endpoint_summary 'Finds all the cats'
107
- endpoint_description 'Returns all cats from the system that the user has access to'
108
- endpoint_tags 'Cats'
109
- endpoint_response 200, ['Cat'], 'Standard response'
110
- endpoint_parameter :size, 'The number of cats to return', :query, false, Integer,
111
- {
112
- :example => 100,
113
- :default => 20, # If the caller send no value the default value will be set in the params
114
- :maximum => 100,
115
- :minimum => 0,
116
- :exclusiveMinimum => true,
117
- }
118
- get '/cats' do
119
- content_type :json
120
- [].to_json
121
- end
122
-
123
- endpoint_summary 'Finds a pet by its id'
124
- endpoint_description 'Finds a pet by its id, or 404 if the user does not have access to the pet'
125
- endpoint_tags 'Pets'
126
- endpoint_response 200, 'Pet', 'Standard response'
127
- endpoint_response 404, 'Error', 'Pet not found'
128
- endpoint_parameter :id, 'The pet id', :path, true, Integer, # Will fail if a non-numerical value is used
129
- {
130
- :example => 1234,
131
- }
132
- get '/pets/:id' do
133
- content_type :json
134
- [404, {:code => 404, :message => 'Pet not found'}.to_json]
135
- end
136
-
137
- # See https://github.com/britg/sinatra-cross_origin/issues/18
138
- options '*' do
139
- response.headers['Allow'] = 'HEAD,GET,PUT,POST,DELETE,OPTIONS'
140
- response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
141
- 200
142
- end
143
-
144
- end
145
-