8ba0492de1462c095aadeec3406892ede009e94a
[arvados.git] / lib / google / api_client / result.rb
1 # Copyright 2010 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 module Google
17   class APIClient
18     ##
19     # This class wraps a result returned by an API call.
20     class Result
21       extend Forwardable
22       
23       ##
24       # Init the result
25       #
26       # @param [Google::APIClient::Request] request
27       #   The original request
28       # @param [Faraday::Response] response
29       #   Raw HTTP Response
30       def initialize(request, response)
31         @request = request
32         @response = response
33         @media_upload = reference if reference.kind_of?(ResumableUpload)
34       end
35
36       # @return [Google::APIClient::Request] Original request object
37       attr_reader :request
38       # @return [Faraday::Response] HTTP response
39       attr_reader :response
40       # @!attribute [r] reference
41       #   @return [Google::APIClient::Request] Original request object
42       #   @deprecated See {#request}
43       alias_method :reference, :request # For compatibility with pre-beta clients
44
45       # @!attribute [r] status
46       #   @return [Integer] HTTP status code
47       # @!attribute [r] headers
48       #   @return [Hash] HTTP response headers
49       # @!attribute [r] body
50       #   @return [String] HTTP response body
51       def_delegators :@response, :status, :headers, :body
52
53       # @!attribute [r] resumable_upload
54       # @return [Google::APIClient::ResumableUpload] For resuming media uploads
55       def resumable_upload        
56         @media_upload ||= (
57           options = self.reference.to_hash.merge(
58             :uri => self.headers['location'],
59             :media => self.reference.media
60           )
61           Google::APIClient::ResumableUpload.new(options)
62         )
63       end
64       
65       ##
66       # Get the content type of the response
67       # @!attribute [r] media_type
68       # @return [String]
69       #  Value of content-type header
70       def media_type
71         _, content_type = self.headers.detect do |h, v|
72           h.downcase == 'Content-Type'.downcase
73         end
74         content_type[/^([^;]*);?.*$/, 1].strip.downcase
75       end
76       
77       ##
78       # Check if request failed
79       #
80       # @!attribute [r] error?
81       # @return [TrueClass, FalseClass]
82       #   true if result of operation is an error
83       def error?
84         return self.response.status >= 400
85       end
86
87       ##
88       # Check if request was successful
89       #
90       # @!attribute [r] success?
91       # @return [TrueClass, FalseClass]
92       #   true if result of operation was successful
93       def success?
94         return !self.error?
95       end
96       
97       ##
98       # Extracts error messages from the response body
99       #
100       # @!attribute [r] error_message
101       # @return [String]
102       #   error message, if available
103       def error_message
104         if self.data?
105           if self.data.respond_to?(:error) &&
106              self.data.error.respond_to?(:message)
107             # You're going to get a terrible error message if the response isn't
108             # parsed successfully as an error.
109             return self.data.error.message
110           elsif self.data['error'] && self.data['error']['message']
111             return self.data['error']['message']
112           end
113         end
114         return self.body
115       end
116
117       ##
118       # Check for parsable data in response
119       #
120       # @!attribute [r] data?
121       # @return [TrueClass, FalseClass]
122       #   true if body can be parsed
123       def data?
124         self.media_type == 'application/json'
125       end
126       
127       ##
128       # Return parsed version of the response body.
129       #
130       # @!attribute [r] data
131       # @return [Object, Hash, String]
132       #   Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
133       def data
134         return @data ||= (begin
135           media_type = self.media_type
136           data = self.body
137           case media_type
138           when 'application/json'
139             data = MultiJson.load(data)
140             # Strip data wrapper, if present
141             data = data['data'] if data.has_key?('data')
142           else
143             raise ArgumentError,
144               "Content-Type not supported for parsing: #{media_type}"
145           end
146           if @request.api_method && @request.api_method.response_schema
147             # Automatically parse using the schema designated for the
148             # response of this API method.
149             data = @request.api_method.response_schema.new(data)
150             data
151           else
152             # Otherwise, return the raw unparsed value.
153             # This value must be indexable like a Hash.
154             data
155           end
156         end)
157       end
158
159       ##
160       # Get the token used for requesting the next page of data
161       #
162       # @!attribute [r] next_page_token
163       # @return [String]
164       #   next page token
165       def next_page_token
166         if self.data.respond_to?(:next_page_token)
167           return self.data.next_page_token
168         elsif self.data.respond_to?(:[])
169           return self.data["nextPageToken"]
170         else
171           raise TypeError, "Data object did not respond to #next_page_token."
172         end
173       end
174
175       ##
176       # Build a request for fetching the next page of data
177       # 
178       # @return [Google::APIClient::Request]
179       #   API request for retrieving next page
180       def next_page
181         merged_parameters = Hash[self.reference.parameters].merge({
182           self.page_token_param => self.next_page_token
183         })
184         # Because Requests can be coerced to Hashes, we can merge them,
185         # preserving all context except the API method parameters that we're
186         # using for pagination.
187         return Google::APIClient::Request.new(
188           Hash[self.reference].merge(:parameters => merged_parameters)
189         )
190       end
191
192       ##
193       # Get the token used for requesting the previous page of data
194       #
195       # @!attribute [r] prev_page_token
196       # @return [String]
197       #   previous page token
198       def prev_page_token
199         if self.data.respond_to?(:prev_page_token)
200           return self.data.prev_page_token
201         elsif self.data.respond_to?(:[])
202           return self.data["prevPageToken"]
203         else
204           raise TypeError, "Data object did not respond to #next_page_token."
205         end
206       end
207
208       ##
209       # Build a request for fetching the previous page of data
210       # 
211       # @return [Google::APIClient::Request]
212       #   API request for retrieving previous page
213       def prev_page
214         merged_parameters = Hash[self.reference.parameters].merge({
215           self.page_token_param => self.prev_page_token
216         })
217         # Because Requests can be coerced to Hashes, we can merge them,
218         # preserving all context except the API method parameters that we're
219         # using for pagination.
220         return Google::APIClient::Request.new(
221           Hash[self.reference].merge(:parameters => merged_parameters)
222         )
223       end
224       
225       ##
226       # Pagination scheme used by this request/response
227       #
228       # @!attribute [r] pagination_type
229       # @return [Symbol]
230       #  currently always :token
231       def pagination_type
232         return :token
233       end
234
235       ##
236       # Name of the field that contains the pagination token
237       #
238       # @!attribute [r] page_token_param
239       # @return [String]
240       #  currently always 'pageToken'
241       def page_token_param
242         return "pageToken"
243       end
244
245     end
246   end
247 end