grape-swagger 1.2.1 → 1.4.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +14 -0
  3. data/.github/workflows/rubocop.yml +26 -0
  4. data/.github/workflows/ruby.yml +32 -0
  5. data/.rubocop.yml +15 -4
  6. data/.rubocop_todo.yml +1 -1
  7. data/CHANGELOG.md +37 -0
  8. data/Gemfile +2 -2
  9. data/README.md +84 -3
  10. data/UPGRADING.md +8 -0
  11. data/grape-swagger.gemspec +3 -3
  12. data/lib/grape-swagger/doc_methods/format_data.rb +2 -2
  13. data/lib/grape-swagger/doc_methods/move_params.rb +1 -1
  14. data/lib/grape-swagger/doc_methods/parse_params.rb +6 -0
  15. data/lib/grape-swagger/doc_methods.rb +65 -62
  16. data/lib/grape-swagger/endpoint.rb +55 -15
  17. data/lib/grape-swagger/errors.rb +2 -0
  18. data/lib/grape-swagger/model_parsers.rb +2 -2
  19. data/lib/grape-swagger/rake/oapi_tasks.rb +2 -0
  20. data/lib/grape-swagger/version.rb +1 -1
  21. data/lib/grape-swagger.rb +5 -2
  22. data/spec/issues/537_enum_values_spec.rb +1 -0
  23. data/spec/issues/776_multiple_presents_spec.rb +59 -0
  24. data/spec/issues/809_utf8_routes_spec.rb +55 -0
  25. data/spec/lib/format_data_spec.rb +24 -0
  26. data/spec/lib/move_params_spec.rb +2 -2
  27. data/spec/spec_helper.rb +1 -1
  28. data/spec/support/empty_model_parser.rb +2 -0
  29. data/spec/support/model_parsers/entity_parser.rb +8 -8
  30. data/spec/support/model_parsers/mock_parser.rb +24 -8
  31. data/spec/support/model_parsers/representable_parser.rb +8 -8
  32. data/spec/support/namespace_tags.rb +3 -0
  33. data/spec/support/the_paths_definitions.rb +4 -4
  34. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +1 -0
  35. data/spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb +4 -2
  36. data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
  37. data/spec/swagger_v2/api_swagger_v2_spec.rb +1 -0
  38. data/spec/swagger_v2/boolean_params_spec.rb +1 -0
  39. data/spec/swagger_v2/float_api_spec.rb +1 -0
  40. data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +1 -0
  41. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +1 -0
  42. data/spec/swagger_v2/param_multi_type_spec.rb +2 -0
  43. data/spec/swagger_v2/param_type_spec.rb +3 -0
  44. data/spec/swagger_v2/param_values_spec.rb +6 -0
  45. data/spec/swagger_v2/{params_array_collection_fromat_spec.rb → params_array_collection_format_spec.rb} +0 -0
  46. data/spec/swagger_v2/params_example_spec.rb +40 -0
  47. data/spec/swagger_v2/reference_entity_spec.rb +2 -2
  48. data/spec/swagger_v2/simple_mounted_api_spec.rb +3 -0
  49. metadata +19 -7
  50. data/.travis.yml +0 -36
@@ -11,7 +11,7 @@ module Grape
11
11
 
12
12
  if content_types.empty?
13
13
  formats = [target_class.format, target_class.default_format].compact.uniq
14
- formats = Grape::Formatter.formatters({}).keys if formats.empty?
14
+ formats = Grape::Formatter.formatters(**{}).keys if formats.empty?
15
15
  content_types = Grape::ContentTypes::CONTENT_TYPES.select do |content_type, _mime_type|
16
16
  formats.include? content_type
17
17
  end.values
@@ -78,11 +78,11 @@ module Grape
78
78
  def path_and_definition_objects(namespace_routes, options)
79
79
  @paths = {}
80
80
  @definitions = {}
81
+ add_definitions_from options[:models]
81
82
  namespace_routes.each_value do |routes|
82
83
  path_item(routes, options)
83
84
  end
84
85
 
85
- add_definitions_from options[:models]
86
86
  [@paths, @definitions]
87
87
  end
88
88
 
@@ -201,26 +201,26 @@ module Grape
201
201
  def response_object(route, options)
202
202
  codes(route).each_with_object({}) do |value, memo|
203
203
  value[:message] ||= ''
204
- memo[value[:code]] = { description: value[:message] }
205
-
204
+ memo[value[:code]] = { description: value[:message] ||= '' } unless memo[value[:code]].present?
206
205
  memo[value[:code]][:headers] = value[:headers] if value[:headers]
207
206
 
208
207
  next build_file_response(memo[value[:code]]) if file_response?(value[:model])
209
208
 
210
- response_model = @item
211
- response_model = expose_params_from_model(value[:model]) if value[:model]
212
-
213
209
  if memo.key?(200) && route.request_method == 'DELETE' && value[:model].nil?
214
210
  memo[204] = memo.delete(200)
215
211
  value[:code] = 204
212
+ next
216
213
  end
217
214
 
218
- next if value[:code] == 204
219
- next unless !response_model.start_with?('Swagger_doc') && (@definitions[response_model] || value[:model])
215
+ # Explicitly request no model with { model: '' }
216
+ next if value[:model] == ''
220
217
 
221
- @definitions[response_model][:description] = description_object(route)
218
+ response_model = value[:model] ? expose_params_from_model(value[:model]) : @item
219
+ next unless @definitions[response_model]
220
+ next if response_model.start_with?('Swagger_doc')
222
221
 
223
- memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
222
+ @definitions[response_model][:description] ||= "#{response_model} model"
223
+ build_memo_schema(memo, route, value, response_model, options)
224
224
  memo[value[:code]][:examples] = value[:examples] if value[:examples]
225
225
  end
226
226
  end
@@ -267,15 +267,52 @@ module Grape
267
267
 
268
268
  private
269
269
 
270
+ def build_memo_schema(memo, route, value, response_model, options)
271
+ if memo[value[:code]][:schema] && value[:as]
272
+ memo[value[:code]][:schema][:properties].merge!(build_reference(route, value, response_model, options))
273
+
274
+ if value[:required]
275
+ memo[value[:code]][:schema][:required] ||= []
276
+ memo[value[:code]][:schema][:required] << value[:as].to_s
277
+ end
278
+
279
+ elsif value[:as]
280
+ memo[value[:code]][:schema] = {
281
+ type: :object,
282
+ properties: build_reference(route, value, response_model, options)
283
+ }
284
+ memo[value[:code]][:schema][:required] = [value[:as].to_s] if value[:required]
285
+ else
286
+ memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
287
+ end
288
+ end
289
+
270
290
  def build_reference(route, value, response_model, settings)
271
291
  # TODO: proof that the definition exist, if model isn't specified
272
- reference = { '$ref' => "#/definitions/#{response_model}" }
292
+ reference = if value.key?(:as)
293
+ { value[:as] => build_reference_hash(response_model) }
294
+ else
295
+ build_reference_hash(response_model)
296
+ end
273
297
  return reference unless value[:code] < 300
274
298
 
275
- reference = { type: 'array', items: reference } if route.options[:is_array]
299
+ if value.key?(:as) && value.key?(:is_array)
300
+ reference[value[:as]] = build_reference_array(reference[value[:as]])
301
+ elsif route.options[:is_array]
302
+ reference = build_reference_array(reference)
303
+ end
304
+
276
305
  build_root(route, reference, response_model, settings)
277
306
  end
278
307
 
308
+ def build_reference_hash(response_model)
309
+ { '$ref' => "#/definitions/#{response_model}" }
310
+ end
311
+
312
+ def build_reference_array(reference)
313
+ { type: 'array', items: reference }
314
+ end
315
+
279
316
  def build_root(route, reference, response_model, settings)
280
317
  default_root = response_model.underscore
281
318
  default_root = default_root.pluralize if route.options[:is_array]
@@ -292,7 +329,7 @@ module Grape
292
329
  end
293
330
 
294
331
  def file_response?(value)
295
- value.to_s.casecmp('file').zero? ? true : false
332
+ value.to_s.casecmp('file').zero?
296
333
  end
297
334
 
298
335
  def build_file_response(memo)
@@ -364,7 +401,7 @@ module Grape
364
401
  end
365
402
 
366
403
  def hidden_parameter?(value)
367
- return false if value.dig(:required)
404
+ return false if value[:required]
368
405
 
369
406
  if value.dig(:documentation, :hidden).is_a?(Proc)
370
407
  value.dig(:documentation, :hidden).call
@@ -381,6 +418,9 @@ module Grape
381
418
  default_code[:message] = entity[:message] || route.description || default_code[:message].sub('{item}', @item)
382
419
  default_code[:examples] = entity[:examples] if entity[:examples]
383
420
  default_code[:headers] = entity[:headers] if entity[:headers]
421
+ default_code[:as] = entity[:as] if entity[:as]
422
+ default_code[:is_array] = entity[:is_array] if entity[:is_array]
423
+ default_code[:required] = entity[:required] if entity[:required]
384
424
  else
385
425
  default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
386
426
  default_code[:model] = entity if entity
@@ -3,7 +3,9 @@
3
3
  module GrapeSwagger
4
4
  module Errors
5
5
  class UnregisteredParser < StandardError; end
6
+
6
7
  class SwaggerSpec < StandardError; end
8
+
7
9
  class SwaggerSpecDeprecated < SwaggerSpec
8
10
  class << self
9
11
  def tell!(what)
@@ -16,14 +16,14 @@ module GrapeSwagger
16
16
  subhash = @parsers.except(klass).to_a
17
17
  insert_at = subhash.index(subhash.assoc(before_klass))
18
18
  insert_at = subhash.length - 1 if insert_at.nil?
19
- @parsers = Hash[subhash.insert(insert_at, [klass, ancestor])]
19
+ @parsers = subhash.insert(insert_at, [klass, ancestor]).to_h
20
20
  end
21
21
 
22
22
  def insert_after(after_klass, klass, ancestor)
23
23
  subhash = @parsers.except(klass).to_a
24
24
  insert_at = subhash.index(subhash.assoc(after_klass))
25
25
  insert_at = subhash.length - 1 if insert_at.nil?
26
- @parsers = Hash[subhash.insert(insert_at + 1, [klass, ancestor])]
26
+ @parsers = subhash.insert(insert_at + 1, [klass, ancestor]).to_h
27
27
  end
28
28
 
29
29
  def each
@@ -74,6 +74,7 @@ module GrapeSwagger
74
74
 
75
75
  # helper methods
76
76
  #
77
+ # rubocop:disable Style/StringConcatenation
77
78
  def make_request
78
79
  get url_for
79
80
 
@@ -83,6 +84,7 @@ module GrapeSwagger
83
84
  )
84
85
  ) + "\n"
85
86
  end
87
+ # rubocop:enable Style/StringConcatenation
86
88
 
87
89
  def url_for
88
90
  oapi_route = api_class.routes[-2]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeSwagger
4
- VERSION = '1.2.1'
4
+ VERSION = '1.4.1'
5
5
  end
data/lib/grape-swagger.rb CHANGED
@@ -29,7 +29,10 @@ module SwaggerRouting
29
29
  route_match = route_path.split(/^.*?#{route.prefix}/).last
30
30
  next unless route_match
31
31
 
32
- route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
32
+ # want to match emojis … ;)
33
+ # route_match = route_match
34
+ # .match('\/([\p{Alnum}|\p{Emoji}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\p{Emoji}|\-|\_]*)$')
35
+ route_match = route_match.match('\/([\p{Alnum}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\-|\_]*)$')
33
36
  next unless route_match
34
37
 
35
38
  resource = route_match.captures.first
@@ -85,7 +88,7 @@ module SwaggerRouting
85
88
  route_name = name.match(%r{^/?([^/]*).*$})[1]
86
89
  return route_name unless route_name.include? ':'
87
90
 
88
- matches = name.match(/\/[a-z]+/)
91
+ matches = name.match(/\/\p{Alpha}+/)
89
92
  matches.nil? ? route_name : matches[0].delete('/')
90
93
  end
91
94
 
@@ -15,6 +15,7 @@ describe '#537 enum values spec' do
15
15
  desc 'create account',
16
16
  success: Spec
17
17
  get do
18
+ { message: 'hi' }
18
19
  end
19
20
  end
20
21
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe '#776 multiple presents spec' do
6
+ include_context "#{MODEL_PARSER} swagger example"
7
+
8
+ let(:app) do
9
+ Class.new(Grape::API) do
10
+ namespace :issue_776 do
11
+ desc 'Get multiple presents',
12
+ success: [
13
+ { model: Entities::EnumValues, as: :gender },
14
+ { model: Entities::Something, as: :somethings, is_array: true, required: true }
15
+ ]
16
+
17
+ get do
18
+ present :gender, { number: 1, gender: 'Male' }, with: Entities::EnumValues
19
+ present :somethings, [
20
+ { id: 1, text: 'element_1', links: %w[link1 link2] },
21
+ { id: 2, text: 'element_2', links: %w[link1 link2] }
22
+ ], with: Entities::Something, is_array: true
23
+ end
24
+ end
25
+
26
+ add_swagger_documentation format: :json
27
+ end
28
+ end
29
+
30
+ subject do
31
+ get '/swagger_doc'
32
+ JSON.parse(last_response.body)
33
+ end
34
+
35
+ let(:definitions) { subject['definitions'] }
36
+ let(:schema) { subject['paths']['/issue_776']['get']['responses']['200']['schema'] }
37
+
38
+ specify { expect(definitions.keys).to include 'EnumValues', 'Something' }
39
+
40
+ specify do
41
+ expect(schema).to eql({
42
+ 'properties' => {
43
+ 'somethings' => {
44
+ 'items' => {
45
+ '$ref' => '#/definitions/Something'
46
+ },
47
+ 'type' => 'array'
48
+ },
49
+ 'gender' => {
50
+ '$ref' => '#/definitions/EnumValues'
51
+ }
52
+ },
53
+ 'type' => 'object',
54
+ 'required' => [
55
+ 'somethings'
56
+ ]
57
+ })
58
+ end
59
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe '#605 root route documentation' do
6
+ let(:app) do
7
+ Class.new(Grape::API) do
8
+ resource :grunnbeløp do
9
+ desc 'returnerer grunnbeløp'
10
+ get do
11
+ { message: 'hello world' }
12
+ end
13
+ end
14
+
15
+ resource :εσόδων do
16
+ desc 'εσόδων'
17
+ get do
18
+ { message: 'hello world' }
19
+ end
20
+ end
21
+
22
+ resource :数 do
23
+ desc '数'
24
+ get do
25
+ { message: 'hello world' }
26
+ end
27
+ end
28
+
29
+ resource :amount do
30
+ desc 'returns amount'
31
+ get do
32
+ { message: 'hello world' }
33
+ end
34
+ end
35
+
36
+ resource :👍 do
37
+ desc 'returns 👍'
38
+ get do
39
+ { message: 'hello world' }
40
+ end
41
+ end
42
+
43
+ add_swagger_documentation
44
+ end
45
+ end
46
+
47
+ subject do
48
+ get '/swagger_doc'
49
+ JSON.parse(last_response.body)['paths']
50
+ end
51
+
52
+ specify do
53
+ expect(subject.keys).to match_array ['/grunnbeløp', '/amount', '/εσόδων', '/数']
54
+ end
55
+ end
@@ -87,5 +87,29 @@ describe GrapeSwagger::DocMethods::FormatData do
87
87
  expect(subject.to_format(params)).to eq expected_params
88
88
  end
89
89
  end
90
+
91
+ context 'when array params are not related' do
92
+ let(:params) do
93
+ [
94
+ { name: 'id', required: true, type: 'string' },
95
+ { name: 'description', required: false, type: 'string' },
96
+ { name: 'ids[]', required: true, type: 'array', items: { type: 'string' } },
97
+ { name: 'user_ids[]', required: true, type: 'array', items: { type: 'string' } }
98
+ ]
99
+ end
100
+
101
+ let(:expected_params) do
102
+ [
103
+ { name: 'id', required: true, type: 'string' },
104
+ { name: 'description', required: false, type: 'string' },
105
+ { name: 'ids[]', required: true, type: 'array', items: { type: 'string' } },
106
+ { name: 'user_ids[]', required: true, type: 'array', items: { type: 'string' } }
107
+ ]
108
+ end
109
+
110
+ it 'parses params correctly and adds array type all concerned elements' do
111
+ expect(subject.to_format(params)).to eq expected_params
112
+ end
113
+ end
90
114
  end
91
115
  end
@@ -97,7 +97,7 @@ describe GrapeSwagger::DocMethods::MoveParams do
97
97
  let(:route_options) { { requirements: {} } }
98
98
  describe 'POST' do
99
99
  let(:params) { paths[path][:post][:parameters] }
100
- let(:route) { Grape::Router::Route.new('POST', path.dup, route_options) }
100
+ let(:route) { Grape::Router::Route.new('POST', path.dup, **route_options) }
101
101
 
102
102
  specify do
103
103
  subject.to_definition(path, params, route, definitions)
@@ -113,7 +113,7 @@ describe GrapeSwagger::DocMethods::MoveParams do
113
113
 
114
114
  describe 'POST' do
115
115
  let(:params) { paths['/in_body/{key}'][:put][:parameters] }
116
- let(:route) { Grape::Router::Route.new('PUT', path.dup, route_options) }
116
+ let(:route) { Grape::Router::Route.new('PUT', path.dup, **route_options) }
117
117
 
118
118
  specify do
119
119
  subject.to_definition(path, params, route, definitions)
data/spec/spec_helper.rb CHANGED
@@ -19,7 +19,7 @@ MODEL_PARSER = ENV.key?('MODEL_PARSER') ? ENV['MODEL_PARSER'].to_s.downcase.sub(
19
19
  require 'grape'
20
20
  require 'grape-swagger'
21
21
 
22
- Dir[File.join(Dir.getwd, 'spec/support/*.rb')].sort.each { |f| require f }
22
+ Dir[File.join(Dir.getwd, 'spec/support/*.rb')].each { |f| require f }
23
23
  require "grape-swagger/#{MODEL_PARSER}" if MODEL_PARSER != 'mock'
24
24
  require File.join(Dir.getwd, "spec/support/model_parsers/#{MODEL_PARSER}_parser.rb")
25
25
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Lint/EmptyClass
3
4
  class EmptyClass
4
5
  end
6
+ # rubocop:enable Lint/EmptyClass
5
7
 
6
8
  module GrapeSwagger
7
9
  class EmptyModelParser
@@ -145,23 +145,23 @@ RSpec.shared_context 'entity swagger example' do
145
145
 
146
146
  let(:swagger_nested_type) do
147
147
  {
148
- 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'status code' }, 'message' => { 'type' => 'string', 'description' => 'error message' } }, 'description' => 'This returns something' },
148
+ 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'status code' }, 'message' => { 'type' => 'string', 'description' => 'error message' } }, 'description' => 'ApiError model' },
149
149
  'ResponseItem' => { 'type' => 'object', 'properties' => { 'id' => { 'type' => 'integer', 'format' => 'int32' }, 'name' => { 'type' => 'string' } } },
150
- 'UseItemResponseAsType' => { 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'responses' => { '$ref' => '#/definitions/ResponseItem' } }, 'description' => 'This returns something' }
150
+ 'UseItemResponseAsType' => { 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'responses' => { '$ref' => '#/definitions/ResponseItem' } }, 'description' => 'UseItemResponseAsType model' }
151
151
  }
152
152
  end
153
153
 
154
154
  let(:swagger_entity_as_response_object) do
155
155
  {
156
- 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'status code' }, 'message' => { 'type' => 'string', 'description' => 'error message' } }, 'description' => 'This returns something' },
156
+ 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'status code' }, 'message' => { 'type' => 'string', 'description' => 'error message' } }, 'description' => 'ApiError model' },
157
157
  'ResponseItem' => { 'type' => 'object', 'properties' => { 'id' => { 'type' => 'integer', 'format' => 'int32' }, 'name' => { 'type' => 'string' } } },
158
- 'UseResponse' => { 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, '$responses' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/ResponseItem' } } }, 'description' => 'This returns something' }
158
+ 'UseResponse' => { 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, '$responses' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/ResponseItem' } } }, 'description' => 'UseResponse model' }
159
159
  }
160
160
  end
161
161
 
162
162
  let(:swagger_params_as_response_object) do
163
163
  {
164
- 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'description' => 'status code', 'type' => 'integer', 'format' => 'int32' }, 'message' => { 'description' => 'error message', 'type' => 'string' } }, 'description' => 'This returns something' }
164
+ 'ApiError' => { 'type' => 'object', 'properties' => { 'code' => { 'description' => 'status code', 'type' => 'integer', 'format' => 'int32' }, 'message' => { 'description' => 'error message', 'type' => 'string' } }, 'description' => 'ApiError model' }
165
165
  }
166
166
  end
167
167
 
@@ -300,7 +300,7 @@ RSpec.shared_context 'entity swagger example' do
300
300
  'type' => 'object',
301
301
  'required' => ['elements'],
302
302
  'properties' => { 'elements' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/QueryInputElement' }, 'description' => 'Set of configuration' } },
303
- 'description' => 'nested route inside namespace'
303
+ 'description' => 'QueryInput model'
304
304
  },
305
305
  'QueryInputElement' => {
306
306
  'type' => 'object',
@@ -310,7 +310,7 @@ RSpec.shared_context 'entity swagger example' do
310
310
  'ApiError' => {
311
311
  'type' => 'object',
312
312
  'properties' => { 'code' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'status code' }, 'message' => { 'type' => 'string', 'description' => 'error message' } },
313
- 'description' => 'This gets Things.'
313
+ 'description' => 'ApiError model'
314
314
  },
315
315
  'Something' => {
316
316
  'type' => 'object',
@@ -320,7 +320,7 @@ RSpec.shared_context 'entity swagger example' do
320
320
  'links' => { 'type' => 'array', 'items' => { 'type' => 'link' } },
321
321
  'others' => { 'type' => 'text' }
322
322
  },
323
- 'description' => 'This gets Things.'
323
+ 'description' => 'Something model'
324
324
  }
325
325
  }
326
326
  }