sinatra-swagger-exposer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +18 -0
- data/lib/sinatra/swagger-exposer/swagger-endpoint-parameter.rb +61 -35
- data/lib/sinatra/swagger-exposer/swagger-endpoint-response.rb +5 -3
- data/lib/sinatra/swagger-exposer/swagger-endpoint.rb +22 -12
- data/lib/sinatra/swagger-exposer/swagger-exposer.rb +55 -12
- data/lib/sinatra/swagger-exposer/swagger-parameter-helper.rb +73 -0
- data/lib/sinatra/swagger-exposer/swagger-parameter-preprocessor.rb +65 -17
- data/lib/sinatra/swagger-exposer/swagger-request-preprocessor.rb +7 -3
- data/lib/sinatra/swagger-exposer/swagger-type-property.rb +1 -1
- data/lib/sinatra/swagger-exposer/swagger-utilities.rb +14 -17
- data/lib/sinatra/swagger-exposer/version.rb +1 -1
- data/sinatra-swagger-exposer.gemspec +3 -1
- metadata +4 -8
- data/.gitignore +0 -10
- data/.travis.yml +0 -6
- data/example/Gemfile +0 -4
- data/example/Gemfile.lock +0 -19
- data/example/config.ru +0 -2
- data/example/petstore.rb +0 -145
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60f345d0ee591c0a428d66ccfb5f5a7f2ae0e9b8
|
4
|
+
data.tar.gz: 4b750003265417b68303fa4f0112fbf0cb3f23a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2f6f72e7ce27da5ebcd9b6517a81b6a9c6cb5cecbda87639d60fabd1142f58ea748315ea220dd7b4779f6bc8e982bee00af770f2a7f267492653af3fee54162
|
7
|
+
data.tar.gz: aadfda04a6b04980d1b875966c6886f4fac0f3a903596b88817742f9038e872fc2b2e2ba853e0c78a11623332befa08d4d4c51894b62a287a5c5f29e553ac177
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
82
|
-
|
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
|
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(
|
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 +
|
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
|
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
|
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,
|
15
|
+
def initialize(type, sinatra_path, parameters, responses, summary, description, tags, explicit_path, produces)
|
16
16
|
@type = type
|
17
|
-
@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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if
|
66
|
-
|
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
|
-
|
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
|
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 ==
|
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 ||
|
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
|
39
|
+
when HOW_TO_PASS_PATH
|
40
|
+
# can't validate
|
41
|
+
when HOW_TO_PASS_QUERY
|
32
42
|
check_param(app.params)
|
33
|
-
when
|
43
|
+
when HOW_TO_PASS_HEADER
|
34
44
|
check_param(app.headers)
|
35
|
-
when
|
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
|
63
|
+
when TYPE_NUMBER
|
54
64
|
return validate_param_value_number(value)
|
55
|
-
when
|
65
|
+
when TYPE_INTEGER
|
56
66
|
return validate_param_value_integer(value)
|
57
|
-
when
|
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
|
-
|
109
|
-
|
125
|
+
PARAMS_MINIMUM,
|
126
|
+
PARAMS_EXCLUSIVE_MINIMUM,
|
110
127
|
'>=',
|
111
128
|
'>')
|
112
129
|
validate_numerical_value_internal(
|
113
130
|
value,
|
114
|
-
|
115
|
-
|
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
|
-
# @
|
122
|
-
# @
|
123
|
-
# @
|
124
|
-
# @
|
125
|
-
# @
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
55
|
+
# @param params [Hash] the parameters
|
64
56
|
# @param allowed_values [Enumerable, #include?] the allowed values
|
65
|
-
# @
|
66
|
-
|
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
|
-
|
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)
|
@@ -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|
|
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.
|
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
|
+
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
data/.travis.yml
DELETED
data/example/Gemfile
DELETED
data/example/Gemfile.lock
DELETED
@@ -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)
|
data/example/config.ru
DELETED
data/example/petstore.rb
DELETED
@@ -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
|
-
|