Sprinkle of logging
[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 [Fixnum] 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         if content_type
75           return content_type[/^([^;]*);?.*$/, 1].strip.downcase
76         else
77           return nil
78         end
79       end
80       
81       ##
82       # Check if request failed
83       #
84       # @!attribute [r] error?
85       # @return [TrueClass, FalseClass]
86       #   true if result of operation is an error
87       def error?
88         return self.response.status >= 400
89       end
90
91       ##
92       # Check if request was successful
93       #
94       # @!attribute [r] success?
95       # @return [TrueClass, FalseClass]
96       #   true if result of operation was successful
97       def success?
98         return !self.error?
99       end
100       
101       ##
102       # Extracts error messages from the response body
103       #
104       # @!attribute [r] error_message
105       # @return [String]
106       #   error message, if available
107       def error_message
108         if self.data?
109           if self.data.respond_to?(:error) &&
110              self.data.error.respond_to?(:message)
111             # You're going to get a terrible error message if the response isn't
112             # parsed successfully as an error.
113             return self.data.error.message
114           elsif self.data['error'] && self.data['error']['message']
115             return self.data['error']['message']
116           end
117         end
118         return self.body
119       end
120
121       ##
122       # Check for parsable data in response
123       #
124       # @!attribute [r] data?
125       # @return [TrueClass, FalseClass]
126       #   true if body can be parsed
127       def data?
128         !(self.body.nil? || self.body.empty? || self.media_type != 'application/json')
129       end
130       
131       ##
132       # Return parsed version of the response body.
133       #
134       # @!attribute [r] data
135       # @return [Object, Hash, String]
136       #   Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
137       def data
138         return @data ||= (begin
139           if self.data?
140             media_type = self.media_type
141             data = self.body
142             case media_type
143             when 'application/json'
144               data = MultiJson.load(data)
145               # Strip data wrapper, if present
146               data = data['data'] if data.has_key?('data')
147             else
148               raise ArgumentError,
149                 "Content-Type not supported for parsing: #{media_type}"
150             end
151             if @request.api_method && @request.api_method.response_schema
152               # Automatically parse using the schema designated for the
153               # response of this API method.
154               data = @request.api_method.response_schema.new(data)
155               data
156             else
157               # Otherwise, return the raw unparsed value.
158               # This value must be indexable like a Hash.
159               data
160             end
161           end
162         end)
163       end
164
165       ##
166       # Get the token used for requesting the next page of data
167       #
168       # @!attribute [r] next_page_token
169       # @return [String]
170       #   next page token
171       def next_page_token
172         if self.data.respond_to?(:next_page_token)
173           return self.data.next_page_token
174         elsif self.data.respond_to?(:[])
175           return self.data["nextPageToken"]
176         else
177           raise TypeError, "Data object did not respond to #next_page_token."
178         end
179       end
180
181       ##
182       # Build a request for fetching the next page of data
183       # 
184       # @return [Google::APIClient::Request]
185       #   API request for retrieving next page
186       def next_page
187         merged_parameters = Hash[self.reference.parameters].merge({
188           self.page_token_param => self.next_page_token
189         })
190         # Because Requests can be coerced to Hashes, we can merge them,
191         # preserving all context except the API method parameters that we're
192         # using for pagination.
193         return Google::APIClient::Request.new(
194           Hash[self.reference].merge(:parameters => merged_parameters)
195         )
196       end
197
198       ##
199       # Get the token used for requesting the previous page of data
200       #
201       # @!attribute [r] prev_page_token
202       # @return [String]
203       #   previous page token
204       def prev_page_token
205         if self.data.respond_to?(:prev_page_token)
206           return self.data.prev_page_token
207         elsif self.data.respond_to?(:[])
208           return self.data["prevPageToken"]
209         else
210           raise TypeError, "Data object did not respond to #next_page_token."
211         end
212       end
213
214       ##
215       # Build a request for fetching the previous page of data
216       # 
217       # @return [Google::APIClient::Request]
218       #   API request for retrieving previous page
219       def prev_page
220         merged_parameters = Hash[self.reference.parameters].merge({
221           self.page_token_param => self.prev_page_token
222         })
223         # Because Requests can be coerced to Hashes, we can merge them,
224         # preserving all context except the API method parameters that we're
225         # using for pagination.
226         return Google::APIClient::Request.new(
227           Hash[self.reference].merge(:parameters => merged_parameters)
228         )
229       end
230       
231       ##
232       # Pagination scheme used by this request/response
233       #
234       # @!attribute [r] pagination_type
235       # @return [Symbol]
236       #  currently always :token
237       def pagination_type
238         return :token
239       end
240
241       ##
242       # Name of the field that contains the pagination token
243       #
244       # @!attribute [r] page_token_param
245       # @return [String]
246       #  currently always 'pageToken'
247       def page_token_param
248         return "pageToken"
249       end
250
251     end
252   end
253 end