homeaway-api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +209 -0
- data/README.md +127 -0
- data/Rakefile +8 -0
- data/bin/hacurl +176 -0
- data/lib/homeaway/api/adapters/faraday.rb +67 -0
- data/lib/homeaway/api/adapters/hurley.rb +83 -0
- data/lib/homeaway/api/client.rb +224 -0
- data/lib/homeaway/api/domain/add_message.rb +45 -0
- data/lib/homeaway/api/domain/client_includes.rb +44 -0
- data/lib/homeaway/api/domain/conversation.rb +39 -0
- data/lib/homeaway/api/domain/listing.rb +39 -0
- data/lib/homeaway/api/domain/listing_reviews.rb +43 -0
- data/lib/homeaway/api/domain/me.rb +35 -0
- data/lib/homeaway/api/domain/my_inbox.rb +58 -0
- data/lib/homeaway/api/domain/my_listings.rb +48 -0
- data/lib/homeaway/api/domain/my_reservations.rb +53 -0
- data/lib/homeaway/api/domain/quote.rb +53 -0
- data/lib/homeaway/api/domain/search.rb +74 -0
- data/lib/homeaway/api/domain/submit_review.rb +57 -0
- data/lib/homeaway/api/errors/ha_api_errors.rb +144 -0
- data/lib/homeaway/api/paginator.rb +140 -0
- data/lib/homeaway/api/response.rb +49 -0
- data/lib/homeaway/api/util/defaults.rb +92 -0
- data/lib/homeaway/api/util/oauth.rb +81 -0
- data/lib/homeaway/api/util/validators.rb +118 -0
- data/lib/homeaway/api/version.rb +20 -0
- data/lib/homeaway_api.rb +43 -0
- metadata +367 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright (c) 2015 HomeAway.com, Inc.
|
2
|
+
# All rights reserved. http://www.homeaway.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'faraday'
|
17
|
+
require 'faraday-http-cache'
|
18
|
+
require 'faraday_middleware/response/follow_redirects'
|
19
|
+
require 'faraday_middleware/instrumentation'
|
20
|
+
require 'faraday_middleware/response/mashify'
|
21
|
+
require 'faraday_middleware/response/parse_json'
|
22
|
+
|
23
|
+
module HomeAway
|
24
|
+
module API
|
25
|
+
# @private
|
26
|
+
module Adapters
|
27
|
+
# @private
|
28
|
+
class FaradayAdapter
|
29
|
+
# @private
|
30
|
+
def self.call(site, opts, headers, method, uri, body, params)
|
31
|
+
agent = Faraday::Connection.new(site, opts) do |conn|
|
32
|
+
conn.headers = headers
|
33
|
+
conn.adapter Faraday.default_adapter
|
34
|
+
conn.options.params_encoder = Faraday::FlatParamsEncoder
|
35
|
+
conn.use Faraday::HttpCache
|
36
|
+
conn.use FaradayMiddleware::Mashify, :mash_class => HomeAway::API::Response
|
37
|
+
conn.use FaradayMiddleware::ParseJson
|
38
|
+
conn.use FaradayMiddleware::Instrumentation
|
39
|
+
conn.use FaradayMiddleware::FollowRedirects
|
40
|
+
end
|
41
|
+
response = agent.send(method, uri) do |req|
|
42
|
+
req.body = body.to_json unless body.empty?
|
43
|
+
req.params = params unless params.empty?
|
44
|
+
end
|
45
|
+
self.transform response
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def self.transform(response)
|
50
|
+
mash = response.body
|
51
|
+
mash = HomeAway::API::Response.new unless mash
|
52
|
+
mash._metadata.headers = HomeAway::API::Response.new(response.headers.to_hash) if response.respond_to? :headers
|
53
|
+
if response.respond_to? :status
|
54
|
+
mash._metadata.status_code = response.status
|
55
|
+
if mash._metadata.status_code.to_i >= 400
|
56
|
+
raise (HomeAway::API::Errors.for_http_code mash._metadata.status_code).new(mash)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
if response.respond_to? :env
|
60
|
+
mash._metadata.request_headers = HomeAway::API::Response.new(response.env.request_headers.to_hash) if response.env.respond_to? :request_headers
|
61
|
+
end
|
62
|
+
mash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright (c) 2015 HomeAway.com, Inc.
|
2
|
+
# All rights reserved. http://www.homeaway.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'hurley'
|
17
|
+
require 'cgi'
|
18
|
+
|
19
|
+
# @private
|
20
|
+
module Hurley
|
21
|
+
# @private
|
22
|
+
class Query
|
23
|
+
# @private
|
24
|
+
def self.default
|
25
|
+
@default ||= Flat
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
module HomeAway
|
32
|
+
module API
|
33
|
+
# @private
|
34
|
+
module Adapters
|
35
|
+
# @private
|
36
|
+
class HurleyAdapter
|
37
|
+
# @private
|
38
|
+
def self.__update_opts(struct, opts)
|
39
|
+
opts.each_pair do |k, v|
|
40
|
+
method = "#{k}=".to_sym
|
41
|
+
if struct.respond_to?(method)
|
42
|
+
struct.send(method, v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @private
|
48
|
+
def self.call(site, opts, headers, method, uri, body, params)
|
49
|
+
begin
|
50
|
+
agent = Hurley::Client.new(site)
|
51
|
+
agent.header.update(headers)
|
52
|
+
self.__update_opts(agent.request_options, opts)
|
53
|
+
self.__update_opts(agent.ssl_options, opts)
|
54
|
+
request = agent.request(method, uri)
|
55
|
+
request.query.update(params) unless params.empty?
|
56
|
+
request.body = body.to_json unless (!body.respond_to?(:to_json) || body.empty?)
|
57
|
+
response = agent.call(request)
|
58
|
+
rescue => e
|
59
|
+
raise HomeAway::API::Errors::HomeAwayAPIError.new(e.message.to_s)
|
60
|
+
end
|
61
|
+
response_to_mash(response)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @private
|
65
|
+
def self.response_to_mash(response)
|
66
|
+
body = response.body ||= {}
|
67
|
+
mash = HomeAway::API::Response.new
|
68
|
+
mash.update JSON.parse(body) unless body.empty?
|
69
|
+
mash._metadata = HomeAway::API::Response.new
|
70
|
+
mash._metadata.headers = response.header.to_hash if response.respond_to? :header
|
71
|
+
if response.respond_to? :status_code
|
72
|
+
mash._metadata.status_code = response.status_code
|
73
|
+
if mash._metadata.status_code.to_i >= 400
|
74
|
+
raise (HomeAway::API::Errors.for_http_code mash._metadata.status_code).new(mash)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
mash.delete(:_metadata) if mash._metadata.empty?
|
78
|
+
mash
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# Copyright (c) 2015 HomeAway.com, Inc.
|
2
|
+
# All rights reserved. http://www.homeaway.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module HomeAway
|
17
|
+
module API
|
18
|
+
class Client
|
19
|
+
|
20
|
+
# @return [Hash] the global default configuration
|
21
|
+
def self.default_configuration
|
22
|
+
@@default_configuration ||= HomeAway::API::Util::Defaults.instance.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pass a block expecting a single hash parameter and set any global configuration settings
|
26
|
+
# that will be inherited by all instances created in the future
|
27
|
+
# @return [Hash] the global default configuration
|
28
|
+
def self.configure
|
29
|
+
yield @@default_configuration if block_given?
|
30
|
+
@@default_configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
include HomeAway::API::Util::OAuth
|
34
|
+
|
35
|
+
attr_accessor :configuration
|
36
|
+
attr_reader :mode, :token, :token_expires
|
37
|
+
|
38
|
+
# @!attribute [rw] configuration
|
39
|
+
# @return [Hash] the current client configuration
|
40
|
+
|
41
|
+
# @!attribute [r] token
|
42
|
+
# @return [String] the current encoded token this client has
|
43
|
+
|
44
|
+
# @!attribute [r] mode
|
45
|
+
# @return [Symbol] the current authentication state of this client. One of either :unauthorized, :two_legged, or :three_legged
|
46
|
+
|
47
|
+
# @!attribute [r] token_expires
|
48
|
+
# @return [Time] the time that the current token in use on this client will expire
|
49
|
+
|
50
|
+
|
51
|
+
# Instantiates a new HomeAway API client
|
52
|
+
#
|
53
|
+
# @option opts [String] :client_id Your HomeAway API OAuth client id. Required here if not set globally
|
54
|
+
# @option opts [String] :client_secret Your HomeAway API OAuth client secret. Required here if not set globally
|
55
|
+
# @option opts [String] :token An existing token if you already have one saved from a previous usage of the api
|
56
|
+
# @return [HomeAway::API::Client] a newly instantiated HomeAway API client
|
57
|
+
def initialize(opts={})
|
58
|
+
@configuration = Hashie::Mash.new(self.class.default_configuration.merge(opts))
|
59
|
+
if opts.has_key?(:token)
|
60
|
+
@configuration[:manual_token_supplied] = true
|
61
|
+
set_token(opts.delete(:token))
|
62
|
+
end
|
63
|
+
validate_configuration
|
64
|
+
logger.debug("client initialized with configuration: #{@configuration}")
|
65
|
+
attempt_auth if @token.nil?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Update the configuration of this particular instance of the client.
|
69
|
+
# Pass a block expecting a single hash parameter to update the configuration settings.
|
70
|
+
# @return [Hash] This client's configuration
|
71
|
+
def configure
|
72
|
+
yield @configuration if block_given?
|
73
|
+
validate_configuration
|
74
|
+
@configuration
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Object] The current logger that has been configured
|
78
|
+
def logger
|
79
|
+
@configuration.logger
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String] The current bearer auth token
|
83
|
+
def token
|
84
|
+
raise HomeAway::API::Errors::HomeAwayAPIError.new('Ticket must be supplied') if @token.nil?
|
85
|
+
@token
|
86
|
+
end
|
87
|
+
|
88
|
+
# @private
|
89
|
+
def marshal_dump
|
90
|
+
# we lose our logger instance naively here, marshal dump doesn't like
|
91
|
+
# one of its fields
|
92
|
+
dump_config = configuration.dup.to_hash
|
93
|
+
dump_config.delete('logger')
|
94
|
+
[@token, token_expires, dump_config]
|
95
|
+
end
|
96
|
+
|
97
|
+
# @private
|
98
|
+
def marshal_load(array)
|
99
|
+
@token = array[0]
|
100
|
+
@token_expires = array[1]
|
101
|
+
@configuration = Hashie::Mash.new(array[2])
|
102
|
+
@configuration.logger = HomeAway::API::Util::Defaults.instance.logger
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [Boolean] Has the token that the client is currently using expired?
|
106
|
+
def token_expired?
|
107
|
+
return false if @configuration[:manual_token_supplied]
|
108
|
+
begin
|
109
|
+
Time.now >= token_expires
|
110
|
+
rescue
|
111
|
+
true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @private
|
116
|
+
def get(url, params={})
|
117
|
+
method :get, url, {}, params
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
def put(url, body, params={})
|
122
|
+
method :put, url, body, params
|
123
|
+
end
|
124
|
+
|
125
|
+
# @private
|
126
|
+
def post(url, body, params={})
|
127
|
+
method :post, url, body, params
|
128
|
+
end
|
129
|
+
|
130
|
+
# @private
|
131
|
+
def delete(url, params={})
|
132
|
+
method :delete, url, {}, params
|
133
|
+
end
|
134
|
+
|
135
|
+
# @private
|
136
|
+
def options(url, params={})
|
137
|
+
method :options, url, {}, params
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def validate_configuration
|
143
|
+
required_uuids = [:client_id, :client_secret]
|
144
|
+
[required_uuids].flatten.each do |required_configuration_directive|
|
145
|
+
raise ArgumentError.new("#{required_configuration_directive.to_s} is required but not supplied") if (@configuration[required_configuration_directive] == nil || @configuration[required_configuration_directive].nil? || !configuration.has_key?(required_configuration_directive))
|
146
|
+
end
|
147
|
+
required_uuids.each do |uuid|
|
148
|
+
HomeAway::API::Util::Validators.uuid(@configuration[uuid])
|
149
|
+
end
|
150
|
+
true
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def adapter
|
155
|
+
registered_adapters = {
|
156
|
+
:faraday => HomeAway::API::Adapters::FaradayAdapter,
|
157
|
+
:hurley => HomeAway::API::Adapters::HurleyAdapter
|
158
|
+
}
|
159
|
+
raise ArgumentError.new("Invalid adapter #{@configuration[:adapter]}") unless registered_adapters.keys.include? @configuration[:adapter]
|
160
|
+
logger.debug("using adapter: #{@configuration[:adapter]}")
|
161
|
+
registered_adapters[@configuration[:adapter]]
|
162
|
+
end
|
163
|
+
|
164
|
+
def headers
|
165
|
+
headers = {}
|
166
|
+
headers['content-type'] = 'application/json'
|
167
|
+
headers['Cache-control'] = @configuration[:cache_control]
|
168
|
+
headers['Authorization'] = "Bearer #{token}"
|
169
|
+
headers['User-Agent'] = "HomeAway API ruby_sdk/#{HomeAway::API::VERSION}"
|
170
|
+
headers['X-HomeAway-RequestMarker'] = SecureRandom.uuid
|
171
|
+
headers['X-HomeAway-TestMode'] = 'true' if @configuration[:test_mode]
|
172
|
+
logger.debug("Sending headers: #{headers.to_json}")
|
173
|
+
headers
|
174
|
+
end
|
175
|
+
|
176
|
+
def method(method, url, body, params)
|
177
|
+
if token_expired?
|
178
|
+
if @configuration[:auto_reauth]
|
179
|
+
logger.info('Token expired and auth_reauth is enabled, attempting 2 legged oauth.')
|
180
|
+
attempt_auth
|
181
|
+
logger.info("Re-authentication attempt completed. Current client status: #{@mode}")
|
182
|
+
else
|
183
|
+
raise HomeAway::API::Errors::TokenExpiredError.new('token is expired, please login again via the oauth url')
|
184
|
+
end
|
185
|
+
end
|
186
|
+
logger.info("#{method.to_s.upcase} to #{url} with params #{params.to_json}")
|
187
|
+
site = @configuration[:api_site] ||= @configuration[:site]
|
188
|
+
response = adapter.call(site, @configuration[:connection_opts], headers, method, url, body, params)
|
189
|
+
logger.debug("returning payload: #{response.to_json}")
|
190
|
+
response
|
191
|
+
end
|
192
|
+
|
193
|
+
def attempt_auth
|
194
|
+
begin
|
195
|
+
two_legged!
|
196
|
+
@mode = :two_legged
|
197
|
+
rescue => e
|
198
|
+
logger.info("failed to perform automatic 2 legged oauth due to: #{e.to_s}")
|
199
|
+
@mode = :unauthorized
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_token(token)
|
204
|
+
@token = token
|
205
|
+
begin
|
206
|
+
listing '100000'
|
207
|
+
begin
|
208
|
+
me
|
209
|
+
logger.info('token supplied on client initialization provided 3-legged oauth')
|
210
|
+
@mode = :three_legged
|
211
|
+
rescue => e
|
212
|
+
logger.info('token supplied on client initialization provided 2-legged oauth')
|
213
|
+
@mode = :two_legged
|
214
|
+
end
|
215
|
+
rescue => e
|
216
|
+
logger.error("token supplied on client initialization was not valid: #{e.to_s}")
|
217
|
+
@mode = :unauthorized
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
require 'homeaway/api/domain/client_includes'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright (c) 2015 HomeAway.com, Inc.
|
2
|
+
# All rights reserved. http://www.homeaway.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module HomeAway
|
17
|
+
module API
|
18
|
+
module Domain
|
19
|
+
module AddMessage
|
20
|
+
|
21
|
+
# Add a Message to a Conversation
|
22
|
+
#
|
23
|
+
# analogous to calling a POST on API url /public/addMessage
|
24
|
+
#
|
25
|
+
# @note user must be logged in via 3 legged oauth to call this function without error
|
26
|
+
#
|
27
|
+
# Headers:
|
28
|
+
# * X-HomeAway-DisplayLocale: If a locale is not specified in a query param, it will be searched for in the X-HomeAway-DisplayLocale Header. If it is not supplied in either area the default locale of the user will be selected if it exists. Otherwise the Accept-Language Header will be used.
|
29
|
+
#
|
30
|
+
# @param conversation_id [String] The Conversation UUID to load.
|
31
|
+
# @param message [String] The body of the message to add to the given conversation.
|
32
|
+
# @return [HomeAway::API::Response] the result of the call to the API
|
33
|
+
def add_message(conversation_id, message, opts={})
|
34
|
+
body = {
|
35
|
+
'message' => message.to_s,
|
36
|
+
}.merge(HomeAway::API::Util::Validators.query_keys(opts))
|
37
|
+
params = {
|
38
|
+
'conversationId' => HomeAway::API::Util::Validators.uuid(conversation_id)
|
39
|
+
}
|
40
|
+
post '/public/addMessage', body, params
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (c) 2015 HomeAway.com, Inc.
|
2
|
+
# All rights reserved. http://www.homeaway.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'homeaway/api/domain/listing'
|
17
|
+
require 'homeaway/api/domain/listing_reviews'
|
18
|
+
require 'homeaway/api/domain/search'
|
19
|
+
require 'homeaway/api/domain/quote'
|
20
|
+
require 'homeaway/api/domain/my_listings'
|
21
|
+
require 'homeaway/api/domain/my_inbox'
|
22
|
+
require 'homeaway/api/domain/submit_review'
|
23
|
+
require 'homeaway/api/domain/me'
|
24
|
+
require 'homeaway/api/domain/my_reservations'
|
25
|
+
require 'homeaway/api/domain/add_message'
|
26
|
+
require 'homeaway/api/domain/conversation'
|
27
|
+
|
28
|
+
module HomeAway
|
29
|
+
module API
|
30
|
+
class Client
|
31
|
+
include HomeAway::API::Domain::Listing
|
32
|
+
include HomeAway::API::Domain::ListingReviews
|
33
|
+
include HomeAway::API::Domain::Search
|
34
|
+
include HomeAway::API::Domain::Quote
|
35
|
+
include HomeAway::API::Domain::MyListings
|
36
|
+
include HomeAway::API::Domain::MyInbox
|
37
|
+
include HomeAway::API::Domain::SubmitReview
|
38
|
+
include HomeAway::API::Domain::Me
|
39
|
+
include HomeAway::API::Domain::MyReservations
|
40
|
+
include HomeAway::API::Domain::AddMessage
|
41
|
+
include HomeAway::API::Domain::Conversation
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|