friendly_shipping 0.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-relaxed.yml +3 -4
  3. data/.rubocop.yml +9 -0
  4. data/CHANGELOG.md +29 -0
  5. data/friendly_shipping.gemspec +12 -12
  6. data/lib/friendly_shipping/api_failure.rb +2 -15
  7. data/lib/friendly_shipping/http_client.rb +16 -9
  8. data/lib/friendly_shipping/request.rb +7 -1
  9. data/lib/friendly_shipping/services/ship_engine/bad_request_handler.rb +15 -3
  10. data/lib/friendly_shipping/services/ups.rb +9 -1
  11. data/lib/friendly_shipping/services/ups/parse_address_classification_response.rb +5 -2
  12. data/lib/friendly_shipping/services/ups/parse_address_validation_response.rb +5 -2
  13. data/lib/friendly_shipping/services/ups/parse_city_state_lookup_response.rb +5 -2
  14. data/lib/friendly_shipping/services/ups/parse_rate_response.rb +5 -1
  15. data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +5 -1
  16. data/lib/friendly_shipping/services/ups/parse_shipment_confirm_response.rb +5 -1
  17. data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +5 -1
  18. data/lib/friendly_shipping/services/ups/parse_void_shipment_response.rb +5 -1
  19. data/lib/friendly_shipping/services/ups/parse_xml_response.rb +16 -7
  20. data/lib/friendly_shipping/services/ups_freight.rb +44 -13
  21. data/lib/friendly_shipping/services/ups_freight/generate_delivery_options_hash.rb +21 -0
  22. data/lib/friendly_shipping/services/ups_freight/generate_document_options_hash.rb +28 -0
  23. data/lib/friendly_shipping/services/ups_freight/generate_email_options_hash.rb +25 -0
  24. data/lib/friendly_shipping/services/ups_freight/generate_freight_rate_request_hash.rb +2 -10
  25. data/lib/friendly_shipping/services/ups_freight/generate_freight_ship_request_hash.rb +81 -0
  26. data/lib/friendly_shipping/services/ups_freight/generate_location_hash.rb +5 -2
  27. data/lib/friendly_shipping/services/ups_freight/generate_pickup_options_hash.rb +21 -0
  28. data/lib/friendly_shipping/services/ups_freight/generate_pickup_request_hash.rb +31 -0
  29. data/lib/friendly_shipping/services/ups_freight/label_delivery_options.rb +29 -0
  30. data/lib/friendly_shipping/services/ups_freight/label_document_options.rb +56 -0
  31. data/lib/friendly_shipping/services/ups_freight/label_email_options.rb +40 -0
  32. data/lib/friendly_shipping/services/ups_freight/label_item_options.rb +10 -0
  33. data/lib/friendly_shipping/services/ups_freight/label_options.rb +37 -0
  34. data/lib/friendly_shipping/services/ups_freight/label_package_options.rb +10 -0
  35. data/lib/friendly_shipping/services/ups_freight/label_pickup_options.rb +29 -0
  36. data/lib/friendly_shipping/services/ups_freight/parse_freight_label_response.rb +57 -0
  37. data/lib/friendly_shipping/services/ups_freight/parse_freight_rate_response.rb +29 -32
  38. data/lib/friendly_shipping/services/ups_freight/parse_shipment_document.rb +24 -0
  39. data/lib/friendly_shipping/services/ups_freight/pickup_request_options.rb +29 -0
  40. data/lib/friendly_shipping/services/ups_freight/rates_options.rb +3 -6
  41. data/lib/friendly_shipping/services/ups_freight/restful_api_error_handler.rb +30 -0
  42. data/lib/friendly_shipping/services/ups_freight/shipment_document.rb +21 -0
  43. data/lib/friendly_shipping/services/ups_freight/shipment_information.rb +35 -0
  44. data/lib/friendly_shipping/services/usps.rb +1 -0
  45. data/lib/friendly_shipping/services/usps/parse_address_validation_response.rb +5 -1
  46. data/lib/friendly_shipping/services/usps/parse_city_state_lookup_response.rb +5 -1
  47. data/lib/friendly_shipping/services/usps/parse_rate_response.rb +5 -2
  48. data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +6 -2
  49. data/lib/friendly_shipping/services/usps/parse_xml_response.rb +15 -5
  50. data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +1 -1
  51. data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +4 -1
  52. data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +11 -5
  53. data/lib/friendly_shipping/services/usps/shipping_methods.rb +4 -4
  54. data/lib/friendly_shipping/version.rb +1 -1
  55. metadata +74 -39
  56. data/lib/friendly_shipping/services/ups_freight/generate_ups_security_hash.rb +0 -23
  57. data/lib/friendly_shipping/services/ups_freight/parse_json_response.rb +0 -38
@@ -15,7 +15,11 @@ module FriendlyShipping
15
15
  # @return [Result<ApiResult<Array<FriendlyShipping::Timing>>>] When successfully parsing, an array of timings in a Success Monad.
16
16
  def call(request:, response:)
17
17
  # Filter out error responses and directly return a failure
18
- parsing_result = ParseXMLResponse.call(response.body, 'SDCGetLocationsResponse')
18
+ parsing_result = ParseXMLResponse.call(
19
+ request: request,
20
+ response: response,
21
+ expected_root_tag: 'SDCGetLocationsResponse'
22
+ )
19
23
  parsing_result.fmap do |xml|
20
24
  expedited_commitments = xml.xpath('//Expedited')
21
25
  expedited_timings = parse_expedited_commitment_nodes(expedited_commitments)
@@ -113,7 +117,7 @@ module FriendlyShipping
113
117
  '1' => 'Priority Mail Express',
114
118
  '2' => 'Priority',
115
119
  '3' => 'First-Class',
116
- '6' => 'First-Class Package Service'
120
+ '6' => 'Package Services'
117
121
  }.freeze
118
122
 
119
123
  # This code carries a few details about the shipment:
@@ -8,18 +8,18 @@ module FriendlyShipping
8
8
  ERROR_TAG = 'Error'
9
9
 
10
10
  class << self
11
- def call(response_body, expected_root_tag)
12
- xml = Nokogiri.XML(response_body, &:strict)
11
+ def call(request:, response:, expected_root_tag:)
12
+ xml = Nokogiri.XML(response.body, &:strict)
13
13
 
14
14
  if xml.root.nil? || ![expected_root_tag, 'Error'].include?(xml.root.name)
15
- Failure('Invalid document')
15
+ wrap_failure('Invalid document', request, response)
16
16
  elsif request_successful?(xml)
17
17
  Success(xml)
18
18
  else
19
- Failure(error_message(xml))
19
+ wrap_failure(error_message(xml), request, response)
20
20
  end
21
21
  rescue Nokogiri::XML::SyntaxError => e
22
- Failure(e)
22
+ wrap_failure(e, request, response)
23
23
  end
24
24
 
25
25
  private
@@ -33,6 +33,16 @@ module FriendlyShipping
33
33
  desc = xml.xpath('//Error/Description')&.text
34
34
  [number, desc].select(&:present?).join(': ').presence&.strip || 'USPS could not process the request.'
35
35
  end
36
+
37
+ def wrap_failure(failure, request, response)
38
+ Failure(
39
+ FriendlyShipping::ApiFailure.new(
40
+ failure,
41
+ original_request: request,
42
+ original_response: response
43
+ )
44
+ )
45
+ end
36
46
  end
37
47
  end
38
48
  end
@@ -8,7 +8,7 @@ module FriendlyShipping
8
8
  #
9
9
  # Context: The shipment object we're trying to get results for
10
10
  # USPS returns rates on a package-by-package basis, so the options for obtaining rates are
11
- # set on the [FriendlyShipping/RateEstimateobect] hash. The possible options are:
11
+ # set on the [FriendlyShipping/RateEstimateObject] hash. The possible options are:
12
12
 
13
13
  # @param [Physical::ShippingMethod] shipping_method The shipping method ("service" in USPS parlance) we want
14
14
  # to get rates for.
@@ -15,7 +15,8 @@ module FriendlyShipping
15
15
  :first_class_mail_type,
16
16
  :hold_for_pickup,
17
17
  :shipping_method,
18
- :transmit_dimensions
18
+ :transmit_dimensions,
19
+ :rectangular
19
20
 
20
21
  def initialize(
21
22
  box_name: :variable,
@@ -24,6 +25,7 @@ module FriendlyShipping
24
25
  hold_for_pickup: false,
25
26
  shipping_method: nil,
26
27
  transmit_dimensions: true,
28
+ rectangular: true,
27
29
  **kwargs
28
30
  )
29
31
  @box_name = CONTAINERS.key?(box_name) ? box_name : :variable
@@ -32,6 +34,7 @@ module FriendlyShipping
32
34
  @hold_for_pickup = hold_for_pickup
33
35
  @shipping_method = shipping_method
34
36
  @transmit_dimensions = transmit_dimensions
37
+ @rectangular = rectangular
35
38
  super kwargs
36
39
  end
37
40
 
@@ -10,11 +10,11 @@ module FriendlyShipping
10
10
 
11
11
  class << self
12
12
  # @param [Physical::Shipment] shipment The shipment we want to get rates for
13
- # shipment.packages[0].properties[:box_name] Can be :rectangular, :variable,
14
- # or a flat rate container defined in CONTAINERS.
13
+ # shipment.packages[0].properties[:box_name] Can be :variable or a
14
+ # flat rate container defined in CONTAINERS.
15
15
  # @param [String] login The USPS login code
16
- # @param [FriendlyShipping::ShippingMethod] shipping_method The shipping method we want to get rates
17
- # for. If empty, we get all of them
16
+ # @param [FriendlyShipping::Services::Usps::RateEstimateOptions] options The options
17
+ # object to use with this request.
18
18
  # @return Array<[FriendlyShipping::Rate]> A set of Rates that this package may be sent with
19
19
  def call(shipment:, login:, options:)
20
20
  xml_builder = Nokogiri::XML::Builder.new do |xml|
@@ -37,7 +37,13 @@ module FriendlyShipping
37
37
  xml.Width("%<width>0.2f" % { width: package.width.convert_to(:inches).value.to_f })
38
38
  xml.Length("%<length>0.2f" % { length: package.length.convert_to(:inches).value.to_f })
39
39
  xml.Height("%<height>0.2f" % { height: package.height.convert_to(:inches).value.to_f })
40
- xml.Girth("%<girth>0.2f" % { girth: girth(package) })
40
+
41
+ # When girth is present, the package is treated as non-rectangular
42
+ # when calculating dimensional weight. This results in a smaller
43
+ # dimensional weight than a rectangular package would have.
44
+ unless package_options.rectangular
45
+ xml.Girth("%<girth>0.2f" % { girth: girth(package) })
46
+ end
41
47
  end
42
48
  xml.Machinable(machinable(package))
43
49
  end
@@ -5,7 +5,6 @@ module FriendlyShipping
5
5
  class Usps
6
6
  CONTAINERS = {
7
7
  variable: 'VARIABLE',
8
- rectangular: 'RECTANGULAR',
9
8
  large_flat_rate_box: 'LG FLAT RATE BOX',
10
9
  medium_flat_rate_box: 'MD FLAT RATE BOX',
11
10
  small_flat_rate_box: 'SM FLAT RATE BOX',
@@ -18,7 +17,7 @@ module FriendlyShipping
18
17
  window_flat_rate_envelope: 'WINDOW FLAT RATE ENVELOPE',
19
18
  small_flat_rate_envelope: 'SM FLAT RATE ENVELOPE',
20
19
  cubic_soft_pack: 'CUBIC SOFT PACK',
21
- cubic_parcels: 'CUBIC_PARCELS'
20
+ cubic_parcels: 'CUBIC PARCELS'
22
21
  }.freeze
23
22
 
24
23
  FIRST_CLASS_MAIL_TYPES = {
@@ -26,12 +25,13 @@ module FriendlyShipping
26
25
  flat: 'FLAT',
27
26
  parcel: 'PARCEL',
28
27
  post_card: 'POSTCARD',
29
- package_service: 'PACKAGESERVICE'
28
+ package_service: 'PACKAGE SERVICE',
29
+ package_service_retail: 'PACKAGE SERVICE RETAIL'
30
30
  }.freeze
31
31
 
32
32
  SHIPPING_METHODS = [
33
33
  'First-Class',
34
- 'First-Class Package Service',
34
+ 'Package Services',
35
35
  'Priority',
36
36
  'Priority Mail Express',
37
37
  'Standard Post',
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FriendlyShipping
4
- VERSION = "0.5"
4
+ VERSION = "0.6.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_shipping
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Meyerhoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-25 00:00:00.000000000 Z
11
+ date: 2020-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: data_uri
@@ -42,34 +42,37 @@ dependencies:
42
42
  name: money
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 6.0.0
47
+ version: '6.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 6.0.0
54
+ version: '6.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: nokogiri
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: '1.6'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.6'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: physical
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
73
76
  - - ">="
74
77
  - !ruby/object:Gem::Version
75
78
  version: 0.4.4
@@ -77,6 +80,9 @@ dependencies:
77
80
  prerelease: false
78
81
  version_requirements: !ruby/object:Gem::Requirement
79
82
  requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '0.4'
80
86
  - - ">="
81
87
  - !ruby/object:Gem::Version
82
88
  version: 0.4.4
@@ -100,28 +106,34 @@ dependencies:
100
106
  requirements:
101
107
  - - ">="
102
108
  - !ruby/object:Gem::Version
103
- version: '0'
109
+ version: '1.17'
110
+ - - "<"
111
+ - !ruby/object:Gem::Version
112
+ version: '3'
104
113
  type: :development
105
114
  prerelease: false
106
115
  version_requirements: !ruby/object:Gem::Requirement
107
116
  requirements:
108
117
  - - ">="
109
118
  - !ruby/object:Gem::Version
110
- version: '0'
119
+ version: '1.17'
120
+ - - "<"
121
+ - !ruby/object:Gem::Version
122
+ version: '3'
111
123
  - !ruby/object:Gem::Dependency
112
124
  name: dotenv
113
125
  requirement: !ruby/object:Gem::Requirement
114
126
  requirements:
115
- - - ">="
127
+ - - "~>"
116
128
  - !ruby/object:Gem::Version
117
- version: '0'
129
+ version: '2.7'
118
130
  type: :development
119
131
  prerelease: false
120
132
  version_requirements: !ruby/object:Gem::Requirement
121
133
  requirements:
122
- - - ">="
134
+ - - "~>"
123
135
  - !ruby/object:Gem::Version
124
- version: '0'
136
+ version: '2.7'
125
137
  - !ruby/object:Gem::Dependency
126
138
  name: factory_bot
127
139
  requirement: !ruby/object:Gem::Requirement
@@ -140,30 +152,30 @@ dependencies:
140
152
  name: pry
141
153
  requirement: !ruby/object:Gem::Requirement
142
154
  requirements:
143
- - - ">="
155
+ - - "~>"
144
156
  - !ruby/object:Gem::Version
145
- version: '0'
157
+ version: '0.12'
146
158
  type: :development
147
159
  prerelease: false
148
160
  version_requirements: !ruby/object:Gem::Requirement
149
161
  requirements:
150
- - - ">="
162
+ - - "~>"
151
163
  - !ruby/object:Gem::Version
152
- version: '0'
164
+ version: '0.12'
153
165
  - !ruby/object:Gem::Dependency
154
166
  name: rake
155
167
  requirement: !ruby/object:Gem::Requirement
156
168
  requirements:
157
- - - "~>"
169
+ - - ">="
158
170
  - !ruby/object:Gem::Version
159
- version: '10.0'
171
+ version: 12.3.3
160
172
  type: :development
161
173
  prerelease: false
162
174
  version_requirements: !ruby/object:Gem::Requirement
163
175
  requirements:
164
- - - "~>"
176
+ - - ">="
165
177
  - !ruby/object:Gem::Version
166
- version: '10.0'
178
+ version: 12.3.3
167
179
  - !ruby/object:Gem::Dependency
168
180
  name: rspec
169
181
  requirement: !ruby/object:Gem::Requirement
@@ -198,56 +210,62 @@ dependencies:
198
210
  requirements:
199
211
  - - ">="
200
212
  - !ruby/object:Gem::Version
201
- version: '0'
213
+ version: '0.80'
214
+ - - "<"
215
+ - !ruby/object:Gem::Version
216
+ version: '1'
202
217
  type: :development
203
218
  prerelease: false
204
219
  version_requirements: !ruby/object:Gem::Requirement
205
220
  requirements:
206
221
  - - ">="
207
222
  - !ruby/object:Gem::Version
208
- version: '0'
223
+ version: '0.80'
224
+ - - "<"
225
+ - !ruby/object:Gem::Version
226
+ version: '1'
209
227
  - !ruby/object:Gem::Dependency
210
228
  name: simplecov
211
229
  requirement: !ruby/object:Gem::Requirement
212
230
  requirements:
213
- - - ">="
231
+ - - "~>"
214
232
  - !ruby/object:Gem::Version
215
- version: '0'
233
+ version: '0.17'
216
234
  type: :development
217
235
  prerelease: false
218
236
  version_requirements: !ruby/object:Gem::Requirement
219
237
  requirements:
220
- - - ">="
238
+ - - "~>"
221
239
  - !ruby/object:Gem::Version
222
- version: '0'
240
+ version: '0.17'
223
241
  - !ruby/object:Gem::Dependency
224
242
  name: vcr
225
243
  requirement: !ruby/object:Gem::Requirement
226
244
  requirements:
227
- - - ">="
245
+ - - "~>"
228
246
  - !ruby/object:Gem::Version
229
- version: '0'
247
+ version: '5.0'
230
248
  type: :development
231
249
  prerelease: false
232
250
  version_requirements: !ruby/object:Gem::Requirement
233
251
  requirements:
234
- - - ">="
252
+ - - "~>"
235
253
  - !ruby/object:Gem::Version
236
- version: '0'
254
+ version: '5.0'
237
255
  - !ruby/object:Gem::Dependency
238
256
  name: webmock
239
257
  requirement: !ruby/object:Gem::Requirement
240
258
  requirements:
241
- - - ">="
259
+ - - "~>"
242
260
  - !ruby/object:Gem::Version
243
- version: '0'
261
+ version: '3.6'
244
262
  type: :development
245
263
  prerelease: false
246
264
  version_requirements: !ruby/object:Gem::Requirement
247
265
  requirements:
248
- - - ">="
266
+ - - "~>"
249
267
  - !ruby/object:Gem::Version
250
- version: '0'
268
+ version: '3.6'
251
269
  description: Allows you to quote or ship a Physical::Shipment object
252
270
  email:
253
271
  - mamhoff@gmail.com
@@ -326,14 +344,31 @@ files:
326
344
  - lib/friendly_shipping/services/ups/timing_options.rb
327
345
  - lib/friendly_shipping/services/ups_freight.rb
328
346
  - lib/friendly_shipping/services/ups_freight/generate_commodity_information.rb
347
+ - lib/friendly_shipping/services/ups_freight/generate_delivery_options_hash.rb
348
+ - lib/friendly_shipping/services/ups_freight/generate_document_options_hash.rb
349
+ - lib/friendly_shipping/services/ups_freight/generate_email_options_hash.rb
329
350
  - lib/friendly_shipping/services/ups_freight/generate_freight_rate_request_hash.rb
351
+ - lib/friendly_shipping/services/ups_freight/generate_freight_ship_request_hash.rb
330
352
  - lib/friendly_shipping/services/ups_freight/generate_location_hash.rb
331
- - lib/friendly_shipping/services/ups_freight/generate_ups_security_hash.rb
353
+ - lib/friendly_shipping/services/ups_freight/generate_pickup_options_hash.rb
354
+ - lib/friendly_shipping/services/ups_freight/generate_pickup_request_hash.rb
355
+ - lib/friendly_shipping/services/ups_freight/label_delivery_options.rb
356
+ - lib/friendly_shipping/services/ups_freight/label_document_options.rb
357
+ - lib/friendly_shipping/services/ups_freight/label_email_options.rb
358
+ - lib/friendly_shipping/services/ups_freight/label_item_options.rb
359
+ - lib/friendly_shipping/services/ups_freight/label_options.rb
360
+ - lib/friendly_shipping/services/ups_freight/label_package_options.rb
361
+ - lib/friendly_shipping/services/ups_freight/label_pickup_options.rb
362
+ - lib/friendly_shipping/services/ups_freight/parse_freight_label_response.rb
332
363
  - lib/friendly_shipping/services/ups_freight/parse_freight_rate_response.rb
333
- - lib/friendly_shipping/services/ups_freight/parse_json_response.rb
364
+ - lib/friendly_shipping/services/ups_freight/parse_shipment_document.rb
365
+ - lib/friendly_shipping/services/ups_freight/pickup_request_options.rb
334
366
  - lib/friendly_shipping/services/ups_freight/rates_item_options.rb
335
367
  - lib/friendly_shipping/services/ups_freight/rates_options.rb
336
368
  - lib/friendly_shipping/services/ups_freight/rates_package_options.rb
369
+ - lib/friendly_shipping/services/ups_freight/restful_api_error_handler.rb
370
+ - lib/friendly_shipping/services/ups_freight/shipment_document.rb
371
+ - lib/friendly_shipping/services/ups_freight/shipment_information.rb
337
372
  - lib/friendly_shipping/services/ups_freight/shipping_methods.rb
338
373
  - lib/friendly_shipping/services/usps.rb
339
374
  - lib/friendly_shipping/services/usps/choose_package_rate.rb
@@ -357,7 +392,7 @@ files:
357
392
  - lib/friendly_shipping/timing.rb
358
393
  - lib/friendly_shipping/types.rb
359
394
  - lib/friendly_shipping/version.rb
360
- homepage: https://github.com/friendly_cart/friendly_shipping
395
+ homepage: https://github.com/friendlycart/friendly_shipping
361
396
  licenses:
362
397
  - MIT
363
398
  metadata: {}
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class UpsFreight
6
- class GenerateUpsSecurityHash
7
- def self.call(login:, password:, key:)
8
- {
9
- UPSSecurity: {
10
- UsernameToken: {
11
- Username: login,
12
- Password: password
13
- },
14
- ServiceAccessToken: {
15
- AccessLicenseNumber: key
16
- }
17
- }
18
- }
19
- end
20
- end
21
- end
22
- end
23
- end