restify 1.15.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -15
  3. data/README.md +23 -31
  4. data/lib/restify/adapter/base.rb +4 -0
  5. data/lib/restify/adapter/telemetry.rb +54 -0
  6. data/lib/restify/adapter/typhoeus.rb +24 -14
  7. data/lib/restify/context.rb +7 -11
  8. data/lib/restify/error.rb +2 -2
  9. data/lib/restify/global.rb +1 -0
  10. data/lib/restify/link.rb +4 -4
  11. data/lib/restify/logging.rb +1 -1
  12. data/lib/restify/processors/base/parsing.rb +5 -24
  13. data/lib/restify/processors/base.rb +1 -1
  14. data/lib/restify/promise.rb +2 -2
  15. data/lib/restify/registry.rb +1 -1
  16. data/lib/restify/relation.rb +45 -17
  17. data/lib/restify/request.rb +6 -6
  18. data/lib/restify/resource.rb +1 -1
  19. data/lib/restify/response.rb +0 -2
  20. data/lib/restify/timeout.rb +2 -2
  21. data/lib/restify/version.rb +4 -4
  22. data/lib/restify.rb +0 -1
  23. data/spec/restify/cache_spec.rb +16 -12
  24. data/spec/restify/context_spec.rb +15 -7
  25. data/spec/restify/error_spec.rb +23 -16
  26. data/spec/restify/features/head_requests_spec.rb +7 -5
  27. data/spec/restify/features/request_bodies_spec.rb +14 -13
  28. data/spec/restify/features/request_errors_spec.rb +2 -2
  29. data/spec/restify/features/request_headers_spec.rb +11 -14
  30. data/spec/restify/features/response_errors_spec.rb +2 -2
  31. data/spec/restify/global_spec.rb +13 -13
  32. data/spec/restify/link_spec.rb +9 -9
  33. data/spec/restify/processors/base_spec.rb +7 -7
  34. data/spec/restify/processors/json_spec.rb +22 -62
  35. data/spec/restify/processors/msgpack_spec.rb +40 -76
  36. data/spec/restify/promise_spec.rb +38 -34
  37. data/spec/restify/registry_spec.rb +6 -8
  38. data/spec/restify/relation_spec.rb +196 -17
  39. data/spec/restify/resource_spec.rb +55 -60
  40. data/spec/restify/timeout_spec.rb +7 -7
  41. data/spec/restify_spec.rb +13 -74
  42. data/spec/spec_helper.rb +13 -17
  43. data/spec/support/stub_server.rb +3 -3
  44. metadata +35 -65
  45. data/lib/restify/adapter/em.rb +0 -139
  46. data/lib/restify/adapter/pooled_em.rb +0 -270
@@ -8,15 +8,15 @@ describe Restify::Timeout do
8
8
  describe '#timeout!' do
9
9
  context 'before having timed out' do
10
10
  it 'do nothing' do
11
- expect { timer.timeout! }.to_not raise_error
11
+ expect { timer.timeout! }.not_to raise_error
12
12
  end
13
13
  end
14
14
 
15
15
  context 'after having timed out' do
16
16
  it 'calls given block' do
17
- expect { timer.timeout! }.to_not raise_error
17
+ expect { timer.timeout! }.not_to raise_error
18
18
  sleep timer.send(:wait_interval)
19
- expect { timer.timeout! }.to raise_error ::Restify::Timeout::Error
19
+ expect { timer.timeout! }.to raise_error Restify::Timeout::Error
20
20
  end
21
21
  end
22
22
  end
@@ -25,13 +25,13 @@ describe Restify::Timeout do
25
25
  it 'calls block on IVar timeout' do
26
26
  expect do
27
27
  timer.wait_on!(Concurrent::IVar.new)
28
- end.to raise_error ::Restify::Timeout::Error
28
+ end.to raise_error Restify::Timeout::Error
29
29
  end
30
30
 
31
31
  it 'calls block on Promise timeout' do
32
32
  expect do
33
33
  timer.wait_on!(Restify::Promise.new)
34
- end.to raise_error ::Restify::Timeout::Error
34
+ end.to raise_error Restify::Timeout::Error
35
35
  end
36
36
 
37
37
  it 'does nothing on successful IVar' do
@@ -40,7 +40,7 @@ describe Restify::Timeout do
40
40
  Thread.new { ivar.set :success }
41
41
  expect(timer.wait_on!(ivar)).to eq :success
42
42
  end
43
- end.to_not raise_error
43
+ end.not_to raise_error
44
44
  end
45
45
 
46
46
  it 'does nothing on successful Promise' do
@@ -48,7 +48,7 @@ describe Restify::Timeout do
48
48
  Restify::Promise.fulfilled(:success).tap do |p|
49
49
  expect(timer.wait_on!(p)).to eq :success
50
50
  end
51
- end.to_not raise_error
51
+ end.not_to raise_error
52
52
  end
53
53
  end
54
54
  end
data/spec/restify_spec.rb CHANGED
@@ -97,7 +97,7 @@ describe Restify do
97
97
  end
98
98
 
99
99
  context 'within threads' do
100
- it 'should consume the API' do
100
+ it 'consumes the API' do
101
101
  # Let's get all users
102
102
 
103
103
  # First request the entry resource usually the
@@ -132,21 +132,21 @@ describe Restify do
132
132
  # Because we forgot to send a "name" the server complains
133
133
  # with an error code that will lead to a raised error.
134
134
 
135
- expect(e.status).to eq :unprocessable_entity
135
+ expect(e.status).to eq :unprocessable_content
136
136
  expect(e.code).to eq 422
137
137
  expect(e.errors).to eq 'name' => ["can't be blank"]
138
138
  end
139
139
 
140
140
  # Let's try again.
141
- created_user = users_relation.post(name: 'John Smith').value!
141
+ created_user = users_relation.post({name: 'John Smith'}).value!
142
142
 
143
143
  # The server returns a 201 Created response with the created
144
144
  # resource.
145
145
  expect(created_user.response.status).to eq :created
146
146
  expect(created_user.response.code).to eq 201
147
147
 
148
- expect(created_user).to have_key :name
149
- expect(created_user.name).to eq 'John Smith'
148
+ expect(created_user).to have_key 'name'
149
+ expect(created_user['name']).to eq 'John Smith'
150
150
 
151
151
  # Let's follow the "Location" header.
152
152
  followed_resource = created_user.follow.get.value!
@@ -154,8 +154,8 @@ describe Restify do
154
154
  expect(followed_resource.response.status).to eq :ok
155
155
  expect(followed_resource.response.code).to eq 200
156
156
 
157
- expect(followed_resource).to have_key :name
158
- expect(followed_resource.name).to eq 'John Smith'
157
+ expect(followed_resource).to have_key 'name'
158
+ expect(followed_resource['name']).to eq 'John Smith'
159
159
 
160
160
  # Now we will fetch a list of all users.
161
161
  users = users_relation.get.value!
@@ -168,80 +168,19 @@ describe Restify do
168
168
 
169
169
  # We have all our attributes and relations here as defined in the
170
170
  # responses from the server.
171
- expect(user).to have_key :name
172
- expect(user[:name]).to eq 'John Smith'
171
+ expect(user).to have_key 'name'
172
+ expect(user['name']).to eq 'John Smith'
173
173
  expect(user).to have_relation :self
174
174
  expect(user).to have_relation :blurb
175
175
 
176
176
  # Let's get the blurb.
177
177
  blurb = user.rel(:blurb).get.value!
178
178
 
179
- expect(blurb).to have_key :title
180
- expect(blurb).to have_key :image
179
+ expect(blurb).to have_key 'title'
180
+ expect(blurb).to have_key 'image'
181
181
 
182
- expect(blurb[:title]).to eq 'Prof. Dr. John Smith'
183
- expect(blurb[:image]).to eq 'http://example.org/avatar.png'
184
- end
185
- end
186
-
187
- context 'within EM-synchrony' do
188
- it 'should consume the API' do
189
- skip 'Seems to be impossible to detect EM scheduled fibers from within'
190
-
191
- EM.synchrony do
192
- root = Restify.new('http://localhost:9292/base').get.value!
193
-
194
- users_relation = root.rel(:users)
195
-
196
- expect(users_relation).to be_a Restify::Relation
197
-
198
- create_user_promise = users_relation.post
199
- expect(create_user_promise).to be_a Restify::Promise
200
-
201
- expect { create_user_promise.value! }.to \
202
- raise_error(Restify::ClientError) do |e|
203
- expect(e.status).to eq :unprocessable_entity
204
- expect(e.code).to eq 422
205
- expect(e.errors).to eq 'name' => ["can't be blank"]
206
- end
207
-
208
- created_user = users_relation.post(name: 'John Smith').value!
209
-
210
- expect(created_user.response.status).to eq :created
211
- expect(created_user.response.code).to eq 201
212
-
213
- expect(created_user).to have_key :name
214
- expect(created_user.name).to eq 'John Smith'
215
-
216
- followed_resource = created_user.follow.get.value!
217
-
218
- expect(followed_resource.response.status).to eq :ok
219
- expect(followed_resource.response.code).to eq 200
220
-
221
- expect(followed_resource).to have_key :name
222
- expect(followed_resource.name).to eq 'John Smith'
223
-
224
- users = users_relation.get.value!
225
-
226
- expect(users).to have(2).items
227
-
228
- user = users.first
229
-
230
- expect(user).to have_key :name
231
- expect(user[:name]).to eq 'John Smith'
232
- expect(user).to have_relation :self
233
- expect(user).to have_relation :blurb
234
-
235
- blurb = user.rel(:blurb).get.value!
236
-
237
- expect(blurb).to have_key :title
238
- expect(blurb).to have_key :image
239
-
240
- expect(blurb[:title]).to eq 'Prof. Dr. John Smith'
241
- expect(blurb[:image]).to eq 'http://example.org/avatar.png'
242
-
243
- EventMachine.stop
244
- end
182
+ expect(blurb['title']).to eq 'Prof. Dr. John Smith'
183
+ expect(blurb['image']).to eq 'http://example.org/avatar.png'
245
184
  end
246
185
  end
247
186
  end
data/spec/spec_helper.rb CHANGED
@@ -4,25 +4,21 @@ require 'rspec'
4
4
  require 'rspec/collection_matchers'
5
5
 
6
6
  require 'simplecov'
7
+ require 'simplecov-cobertura'
8
+
7
9
  SimpleCov.start do
8
10
  add_filter 'spec'
9
11
  end
10
12
 
11
- if ENV['CI']
12
- require 'codecov'
13
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
14
- end
13
+ SimpleCov.formatters = [
14
+ SimpleCov::Formatter::HTMLFormatter,
15
+ SimpleCov::Formatter::CoberturaFormatter,
16
+ ]
15
17
 
16
18
  require 'restify'
17
19
 
18
20
  if ENV['ADAPTER']
19
21
  case ENV['ADAPTER'].to_s.downcase
20
- when 'em'
21
- require 'restify/adapter/em'
22
- Restify.adapter = Restify::Adapter::EM.new
23
- when 'em-pooled'
24
- require 'restify/adapter/pooled_em'
25
- Restify.adapter = Restify::Adapter::PooledEM.new
26
22
  when 'typhoeus'
27
23
  require 'restify/adapter/typhoeus'
28
24
  Restify.adapter = Restify::Adapter::Typhoeus.new
@@ -40,21 +36,21 @@ RSpec.configure do |config|
40
36
  config.order = 'random'
41
37
 
42
38
  config.before(:suite) do
43
- ::Restify::Timeout.default_timeout = 1.0
39
+ Restify::Timeout.default_timeout = 1.0
44
40
  end
45
41
 
46
- config.before(:each) do |example|
42
+ config.before do |example|
47
43
  next unless (adapter = example.metadata[:adapter])
48
- next if Restify.adapter.is_a?(adapter)
44
+ next if Restify.adapter.class.ancestors.map(&:to_s).include?(adapter)
49
45
 
50
46
  skip 'Spec not enabled for current adapter'
51
47
  end
52
48
 
53
- config.before(:each) do
54
- Ethon.logger = ::Logging.logger[Ethon] if defined?(Ethon)
49
+ config.before do
50
+ Ethon.logger = Logging.logger[Ethon] if defined?(Ethon)
55
51
 
56
- ::Logging.logger.root.level = :debug
57
- ::Logging.logger.root.add_appenders ::Logging.appenders.stdout
52
+ Logging.logger.root.level = :debug
53
+ Logging.logger.root.add_appenders Logging.appenders.stdout
58
54
  end
59
55
 
60
56
  config.warnings = true
@@ -17,10 +17,10 @@ module Stub
17
17
  #
18
18
  # If no stub is found a special HTTP 599 error code will be returned.
19
19
  class Handler
20
- def call(env) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
20
+ def call(env)
21
21
  signature = WebMock::RequestSignature.new(
22
22
  env['REQUEST_METHOD'].downcase,
23
- "http://stubserver#{env['REQUEST_URI']}"
23
+ "http://stubserver#{env['REQUEST_URI']}",
24
24
  )
25
25
 
26
26
  # Extract request headers from rack env. Most header should start with
@@ -99,7 +99,7 @@ RSpec.configure do |config|
99
99
  WebMock.disable!(except: %i[net_http])
100
100
  end
101
101
 
102
- config.around(:each) do |example|
102
+ config.around do |example|
103
103
  example.run
104
104
  WebMock.reset!
105
105
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-07-15 00:00:00.000000000 Z
10
+ date: 2025-02-14 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -53,83 +52,77 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '1.0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: hashie
55
+ name: hitimes
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - ">="
60
59
  - !ruby/object:Gem::Version
61
- version: '3.3'
62
- - - "<"
63
- - !ruby/object:Gem::Version
64
- version: '5.0'
60
+ version: '0'
65
61
  type: :runtime
66
62
  prerelease: false
67
63
  version_requirements: !ruby/object:Gem::Requirement
68
64
  requirements:
69
65
  - - ">="
70
66
  - !ruby/object:Gem::Version
71
- version: '3.3'
72
- - - "<"
73
- - !ruby/object:Gem::Version
74
- version: '5.0'
67
+ version: '0'
75
68
  - !ruby/object:Gem::Dependency
76
- name: rack
69
+ name: logging
77
70
  requirement: !ruby/object:Gem::Requirement
78
71
  requirements:
79
- - - ">="
72
+ - - "~>"
80
73
  - !ruby/object:Gem::Version
81
- version: '0'
74
+ version: '2.0'
82
75
  type: :runtime
83
76
  prerelease: false
84
77
  version_requirements: !ruby/object:Gem::Requirement
85
78
  requirements:
86
- - - ">="
79
+ - - "~>"
87
80
  - !ruby/object:Gem::Version
88
- version: '0'
81
+ version: '2.0'
89
82
  - !ruby/object:Gem::Dependency
90
- name: typhoeus
83
+ name: msgpack
91
84
  requirement: !ruby/object:Gem::Requirement
92
85
  requirements:
93
86
  - - "~>"
94
87
  - !ruby/object:Gem::Version
95
- version: '1.3'
88
+ version: '1.2'
96
89
  type: :runtime
97
90
  prerelease: false
98
91
  version_requirements: !ruby/object:Gem::Requirement
99
92
  requirements:
100
93
  - - "~>"
101
94
  - !ruby/object:Gem::Version
102
- version: '1.3'
95
+ version: '1.2'
103
96
  - !ruby/object:Gem::Dependency
104
- name: logging
97
+ name: opentelemetry-api
105
98
  requirement: !ruby/object:Gem::Requirement
106
99
  requirements:
107
100
  - - "~>"
108
101
  - !ruby/object:Gem::Version
109
- version: '2.0'
102
+ version: '1.0'
110
103
  type: :runtime
111
104
  prerelease: false
112
105
  version_requirements: !ruby/object:Gem::Requirement
113
106
  requirements:
114
107
  - - "~>"
115
108
  - !ruby/object:Gem::Version
116
- version: '2.0'
109
+ version: '1.0'
117
110
  - !ruby/object:Gem::Dependency
118
- name: msgpack
111
+ name: opentelemetry-common
119
112
  requirement: !ruby/object:Gem::Requirement
120
113
  requirements:
121
- - - "~>"
114
+ - - ">="
122
115
  - !ruby/object:Gem::Version
123
- version: '1.2'
116
+ version: '0'
124
117
  type: :runtime
125
118
  prerelease: false
126
119
  version_requirements: !ruby/object:Gem::Requirement
127
120
  requirements:
128
- - - "~>"
121
+ - - ">="
129
122
  - !ruby/object:Gem::Version
130
- version: '1.2'
123
+ version: '0'
131
124
  - !ruby/object:Gem::Dependency
132
- name: hitimes
125
+ name: rack
133
126
  requirement: !ruby/object:Gem::Requirement
134
127
  requirements:
135
128
  - - ">="
@@ -143,23 +136,23 @@ dependencies:
143
136
  - !ruby/object:Gem::Version
144
137
  version: '0'
145
138
  - !ruby/object:Gem::Dependency
146
- name: bundler
139
+ name: typhoeus
147
140
  requirement: !ruby/object:Gem::Requirement
148
141
  requirements:
149
- - - ">="
142
+ - - "~>"
150
143
  - !ruby/object:Gem::Version
151
- version: '0'
152
- type: :development
144
+ version: '1.3'
145
+ type: :runtime
153
146
  prerelease: false
154
147
  version_requirements: !ruby/object:Gem::Requirement
155
148
  requirements:
156
- - - ">="
149
+ - - "~>"
157
150
  - !ruby/object:Gem::Version
158
- version: '0'
151
+ version: '1.3'
159
152
  description: An experimental hypermedia REST client that uses parallel, keep-alive
160
153
  and pipelined requests by default.
161
154
  email:
162
- - jg@altimos.de
155
+ - jgraichen@altimos.de
163
156
  executables: []
164
157
  extensions: []
165
158
  extra_rdoc_files: []
@@ -169,8 +162,7 @@ files:
169
162
  - README.md
170
163
  - lib/restify.rb
171
164
  - lib/restify/adapter/base.rb
172
- - lib/restify/adapter/em.rb
173
- - lib/restify/adapter/pooled_em.rb
165
+ - lib/restify/adapter/telemetry.rb
174
166
  - lib/restify/adapter/typhoeus.rb
175
167
  - lib/restify/cache.rb
176
168
  - lib/restify/context.rb
@@ -214,8 +206,8 @@ files:
214
206
  homepage: https://github.com/jgraichen/restify
215
207
  licenses:
216
208
  - LGPL-3.0+
217
- metadata: {}
218
- post_install_message:
209
+ metadata:
210
+ rubygems_mfa_required: 'true'
219
211
  rdoc_options: []
220
212
  require_paths:
221
213
  - lib
@@ -223,36 +215,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
223
215
  requirements:
224
216
  - - ">="
225
217
  - !ruby/object:Gem::Version
226
- version: 2.5.0
218
+ version: 3.1.0
227
219
  required_rubygems_version: !ruby/object:Gem::Requirement
228
220
  requirements:
229
221
  - - ">="
230
222
  - !ruby/object:Gem::Version
231
223
  version: '0'
232
224
  requirements: []
233
- rubygems_version: 3.1.6
234
- signing_key:
225
+ rubygems_version: 3.6.2
235
226
  specification_version: 4
236
227
  summary: An experimental hypermedia REST client.
237
- test_files:
238
- - spec/restify/cache_spec.rb
239
- - spec/restify/context_spec.rb
240
- - spec/restify/error_spec.rb
241
- - spec/restify/features/head_requests_spec.rb
242
- - spec/restify/features/request_bodies_spec.rb
243
- - spec/restify/features/request_errors_spec.rb
244
- - spec/restify/features/request_headers_spec.rb
245
- - spec/restify/features/response_errors_spec.rb
246
- - spec/restify/global_spec.rb
247
- - spec/restify/link_spec.rb
248
- - spec/restify/processors/base_spec.rb
249
- - spec/restify/processors/json_spec.rb
250
- - spec/restify/processors/msgpack_spec.rb
251
- - spec/restify/promise_spec.rb
252
- - spec/restify/registry_spec.rb
253
- - spec/restify/relation_spec.rb
254
- - spec/restify/resource_spec.rb
255
- - spec/restify/timeout_spec.rb
256
- - spec/restify_spec.rb
257
- - spec/spec_helper.rb
258
- - spec/support/stub_server.rb
228
+ test_files: []
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'eventmachine'
4
- require 'em-http-request'
5
-
6
- module Restify
7
- module Adapter
8
- class EM < Base
9
- class Connection
10
- class << self
11
- def open(uri)
12
- connections[uri.origin] ||= new uri.origin
13
- end
14
-
15
- def connections
16
- @connections ||= {}
17
- end
18
- end
19
-
20
- attr_reader :origin
21
-
22
- def initialize(origin)
23
- @origin = origin
24
- @pipeline = true
25
- end
26
-
27
- def requests
28
- @requests ||= []
29
- end
30
-
31
- # rubocop:disable Style/IdenticalConditionalBranches
32
- def call(request, writer, retried = false)
33
- if requests.empty?
34
- requests << [request, writer, retried]
35
- process_next
36
- else
37
- requests << [request, writer, retried]
38
- end
39
- end
40
- # rubocop:enable all
41
-
42
- def connection
43
- @connection ||= EventMachine::HttpRequest.new(origin)
44
- end
45
-
46
- def pipeline?
47
- @pipeline
48
- end
49
-
50
- # rubocop:disable Metrics/AbcSize
51
- # rubocop:disable Metrics/CyclomaticComplexity
52
- # rubocop:disable Metrics/MethodLength
53
- # rubocop:disable Metrics/PerceivedComplexity
54
- def process_next
55
- return if requests.empty?
56
-
57
- request, writer, retried = pipeline? ? requests.shift : requests.first
58
- begin
59
- req = connection.send request.method.downcase,
60
- keepalive: true,
61
- redirects: 3,
62
- path: request.uri.normalized_path,
63
- query: request.uri.normalized_query,
64
- body: request.body,
65
- head: request.headers
66
- rescue Exception => e # rubocop:disable Lint/RescueException
67
- writer.reject e
68
- requests.shift unless pipeline?
69
- return
70
- end
71
-
72
- req.callback do
73
- requests.shift unless pipeline?
74
-
75
- writer.fulfill Response.new(
76
- request,
77
- req.last_effective_url,
78
- req.response_header.status,
79
- req.response_header,
80
- req.response
81
- )
82
-
83
- if req.response_header['CONNECTION'] == 'close'
84
- @connection = nil
85
- @pipeline = false
86
- end
87
-
88
- process_next
89
- end
90
-
91
- req.errback do
92
- requests.shift unless pipeline?
93
- @connection = nil
94
-
95
- if pipeline?
96
- EventMachine.next_tick do
97
- @pipeline = false
98
- call request, writer
99
- end
100
- elsif !retried
101
- EventMachine.next_tick { call request, writer }
102
- else
103
- begin
104
- raise "(#{req.response_header.status}) #{req.error}"
105
- rescue StandardError => e
106
- writer.reject e
107
- end
108
- end
109
- end
110
- end
111
- # rubocop:enable all
112
- end
113
-
114
- def call_native(request, writer)
115
- next_tick do
116
- Connection.open(request.uri).call(request, writer)
117
- end
118
- end
119
-
120
- private
121
-
122
- def next_tick(&block)
123
- ensure_running
124
- EventMachine.next_tick(&block)
125
- end
126
-
127
- def ensure_running
128
- return if EventMachine.reactor_running?
129
-
130
- Thread.new do
131
- EventMachine.run {}
132
- rescue StandardError => e
133
- puts "#{self.class} -> #{e}\n#{e.backtrace.join("\n")}"
134
- raise e
135
- end
136
- end
137
- end
138
- end
139
- end