Merge remote branch 'remotes/upstream/master'
[arvados.git] / lib / google / api_client / reference.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 require 'stringio'
16 require 'json'
17 require 'addressable/uri'
18 require 'google/api_client/discovery'
19
20 module Google
21   class APIClient
22     class Reference
23       def initialize(options={})
24         # We only need this to do lookups on method ID String values
25         # It's optional, but method ID lookups will fail if the client is
26         # omitted.
27         @client = options[:client]
28         @version = options[:version] || 'v1'
29
30         self.api_method = options[:api_method]
31         self.parameters = options[:parameters] || {}
32         # These parameters are handled differently because they're not
33         # parameters to the API method, but rather to the API system.
34         self.parameters['key'] ||= options[:key] if options[:key]
35         self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
36         self.headers = options[:headers] || []
37         if options[:body]
38           self.body = options[:body]
39         elsif options[:merged_body]
40           self.merged_body = options[:merged_body]
41         elsif options[:body_object]
42           if options[:body_object].respond_to?(:to_json)
43             serialized_body = options[:body_object].to_json
44           elsif options[:body_object].respond_to?(:to_hash)
45             serialized_body = JSON.generate(options[:body_object].to_hash)
46           else
47             raise TypeError,
48               'Could not convert body object to JSON.' +
49               'Must respond to :to_json or :to_hash.'
50           end
51           self.merged_body = serialized_body
52         else
53           self.merged_body = ''
54         end
55         unless self.api_method
56           self.http_method = options[:http_method] || 'GET'
57           self.uri = options[:uri]
58           unless self.parameters.empty?
59             self.uri.query_values =
60               (self.uri.query_values || {}).merge(self.parameters)
61           end
62         end
63       end
64
65       def api_method
66         return @api_method
67       end
68
69       def api_method=(new_api_method)
70         if new_api_method.kind_of?(Google::APIClient::Method) ||
71             new_api_method == nil
72           @api_method = new_api_method
73         elsif new_api_method.respond_to?(:to_str) ||
74             new_api_method.kind_of?(Symbol)
75           unless @client
76             raise ArgumentError,
77               "API method lookup impossible without client instance."
78           end
79           new_api_method = new_api_method.to_s
80           # This method of guessing the API is unreliable. This will fail for
81           # APIs where the first segment of the RPC name does not match the
82           # service name. However, this is a fallback mechanism anyway.
83           # Developers should be passing in a reference to the method, rather
84           # than passing in a string or symbol. This should raise an error
85           # in the case of a mismatch.
86           api = new_api_method[/^([^.]+)\./, 1]
87           @api_method = @client.discovered_method(
88             new_api_method, api, @version
89           )
90           if @api_method
91             # Ditch the client reference, we won't need it again.
92             @client = nil
93           else
94             raise ArgumentError, "API method could not be found."
95           end
96         else
97           raise TypeError,
98             "Expected Google::APIClient::Method, got #{new_api_method.class}."
99         end
100       end
101
102       def parameters
103         return @parameters
104       end
105
106       def parameters=(new_parameters)
107         # No type-checking needed, the Method class handles this.
108         @parameters = new_parameters
109       end
110
111       def body
112         return @body
113       end
114
115       def body=(new_body)
116         if new_body.respond_to?(:each)
117           @body = new_body
118         else
119           raise TypeError, "Expected body to respond to :each."
120         end
121       end
122
123       def merged_body
124         return (self.body.inject(StringIO.new) do |accu, chunk|
125           accu.write(chunk)
126           accu
127         end).string
128       end
129
130       def merged_body=(new_merged_body)
131         if new_merged_body.respond_to?(:string)
132           new_merged_body = new_merged_body.string
133         elsif new_merged_body.respond_to?(:to_str)
134           new_merged_body = new_merged_body.to_str
135         else
136           raise TypeError,
137             "Expected String or StringIO, got #{new_merged_body.class}."
138         end
139         self.body = [new_merged_body]
140       end
141
142       def headers
143         return @headers ||= []
144       end
145
146       def headers=(new_headers)
147         if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
148           @headers = new_headers
149         else
150           raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
151         end
152       end
153
154       def http_method
155         return @http_method ||= self.api_method.http_method
156       end
157
158       def http_method=(new_http_method)
159         if new_http_method.kind_of?(Symbol)
160           @http_method = new_http_method.to_s.upcase
161         elsif new_http_method.respond_to?(:to_str)
162           @http_method = new_http_method.to_str.upcase
163         else
164           raise TypeError,
165             "Expected String or Symbol, got #{new_http_method.class}."
166         end
167       end
168
169       def uri
170         return @uri ||= self.api_method.generate_uri(self.parameters)
171       end
172
173       def uri=(new_uri)
174         @uri = Addressable::URI.parse(new_uri)
175       end
176
177       def to_request
178         if self.api_method
179           return self.api_method.generate_request(
180             self.parameters, self.merged_body, self.headers
181           )
182         else
183           return [self.http_method, self.uri, self.headers, self.body]
184         end
185       end
186
187       def to_hash
188         options = {}
189         if self.api_method
190           options[:api_method] = self.api_method
191           options[:parameters] = self.parameters
192         else
193           options[:http_method] = self.http_method
194           options[:uri] = self.uri
195         end
196         options[:headers] = self.headers
197         options[:body] = self.body
198         return options
199       end
200     end
201   end
202 end