Unify processing of api/resumable/batch requests
[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       def initialize(reference, response)
22         @reference = reference
23         @response = response
24         @media_upload = reference if reference.kind_of?(ResumableUpload)
25       end
26
27       attr_reader :reference
28
29       attr_reader :response
30
31       def status
32         return @response.status
33       end
34
35       def headers
36         return @response.headers
37       end
38
39       def body
40         return @response.body
41       end
42
43       def resumable_upload        
44         @media_upload ||= (
45           options = self.reference.to_hash.merge(
46             :uri => self.headers['location'],
47             :media => self.reference.media
48           )
49           Google::APIClient::ResumableUpload.new(options)
50         )
51       end
52       
53       def media_type
54         _, content_type = self.headers.detect do |h, v|
55           h.downcase == 'Content-Type'.downcase
56         end
57         content_type[/^([^;]*);?.*$/, 1].strip.downcase
58       end
59       
60       def error?
61         return self.response.status >= 400
62       end
63
64       def success?
65         return !self.error?
66       end
67       
68       def error_message
69         if self.data?
70           if self.data.respond_to?(:error) &&
71              self.data.error.respond_to?(:message)
72             # You're going to get a terrible error message if the response isn't
73             # parsed successfully as an error.
74             return self.data.error.message
75           elsif self.data['error'] && self.data['error']['message']
76             return self.data['error']['message']
77           end
78         end
79         return self.body
80       end
81       
82       def data?
83         self.media_type == 'application/json'
84       end
85       
86       def data
87         return @data ||= (begin
88           media_type = self.media_type
89           data = self.body
90           case media_type
91           when 'application/json'
92             data = MultiJson.load(data)
93             # Strip data wrapper, if present
94             data = data['data'] if data.has_key?('data')
95           else
96             raise ArgumentError,
97               "Content-Type not supported for parsing: #{media_type}"
98           end
99           if @reference.api_method && @reference.api_method.response_schema
100             # Automatically parse using the schema designated for the
101             # response of this API method.
102             data = @reference.api_method.response_schema.new(data)
103             data
104           else
105             # Otherwise, return the raw unparsed value.
106             # This value must be indexable like a Hash.
107             data
108           end
109         end)
110       end
111
112       def pagination_type
113         return :token
114       end
115
116       def page_token_param
117         return "pageToken"
118       end
119
120       def next_page_token
121         if self.data.respond_to?(:next_page_token)
122           return self.data.next_page_token
123         elsif self.data.respond_to?(:[])
124           return self.data["nextPageToken"]
125         else
126           raise TypeError, "Data object did not respond to #next_page_token."
127         end
128       end
129
130       def next_page
131         merged_parameters = Hash[self.reference.parameters].merge({
132           self.page_token_param => self.next_page_token
133         })
134         # Because Requests can be coerced to Hashes, we can merge them,
135         # preserving all context except the API method parameters that we're
136         # using for pagination.
137         return Google::APIClient::Request.new(
138           Hash[self.reference].merge(:parameters => merged_parameters)
139         )
140       end
141
142       def prev_page_token
143         if self.data.respond_to?(:prev_page_token)
144           return self.data.prev_page_token
145         elsif self.data.respond_to?(:[])
146           return self.data["prevPageToken"]
147         else
148           raise TypeError, "Data object did not respond to #next_page_token."
149         end
150       end
151
152       def prev_page
153         merged_parameters = Hash[self.reference.parameters].merge({
154           self.page_token_param => self.prev_page_token
155         })
156         # Because Requests can be coerced to Hashes, we can merge them,
157         # preserving all context except the API method parameters that we're
158         # using for pagination.
159         return Google::APIClient::Request.new(
160           Hash[self.reference].merge(:parameters => merged_parameters)
161         )
162       end
163     end
164   end
165 end