Merge branch 'master' of https://github.com/google/google-api-ruby-client
[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, nil if no page token available
186       def next_page
187         return nil unless self.next_page_token
188         merged_parameters = Hash[self.reference.parameters].merge({
189           self.page_token_param => self.next_page_token
190         })
191         # Because Requests can be coerced to Hashes, we can merge them,
192         # preserving all context except the API method parameters that we're
193         # using for pagination.
194         return Google::APIClient::Request.new(
195           Hash[self.reference].merge(:parameters => merged_parameters)
196         )
197       end
198
199       ##
200       # Get the token used for requesting the previous page of data
201       #
202       # @!attribute [r] prev_page_token
203       # @return [String]
204       #   previous page token
205       def prev_page_token
206         if self.data.respond_to?(:prev_page_token)
207           return self.data.prev_page_token
208         elsif self.data.respond_to?(:[])
209           return self.data["prevPageToken"]
210         else
211           raise TypeError, "Data object did not respond to #next_page_token."
212         end
213       end
214
215       ##
216       # Build a request for fetching the previous page of data
217       # 
218       # @return [Google::APIClient::Request]
219       #   API request for retrieving previous page, nil if no page token available
220       def prev_page
221         return nil unless self.prev_page_token
222         merged_parameters = Hash[self.reference.parameters].merge({
223           self.page_token_param => self.prev_page_token
224         })
225         # Because Requests can be coerced to Hashes, we can merge them,
226         # preserving all context except the API method parameters that we're
227         # using for pagination.
228         return Google::APIClient::Request.new(
229           Hash[self.reference].merge(:parameters => merged_parameters)
230         )
231       end
232       
233       ##
234       # Pagination scheme used by this request/response
235       #
236       # @!attribute [r] pagination_type
237       # @return [Symbol]
238       #  currently always :token
239       def pagination_type
240         return :token
241       end
242
243       ##
244       # Name of the field that contains the pagination token
245       #
246       # @!attribute [r] page_token_param
247       # @return [String]
248       #  currently always 'pageToken'
249       def page_token_param
250         return "pageToken"
251       end
252
253     end
254   end
255 end