diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb
index 6da8ca616..378c56025 100644
--- a/lib/google/api_client.rb
+++ b/lib/google/api_client.rb
@@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'httpadapter'
+require 'json'
+
+require 'google/api_client/discovery/service'
+
module Google #:nodoc:
##
# This class manages communication with a single API.
@@ -21,11 +26,22 @@ module Google #:nodoc:
@options = {
# TODO: What configuration options need to go here?
}.merge(options)
+ end
+
+ ##
+ # Returns the parser used by the client.
+ def parser
unless @options[:parser]
require 'google/api_client/parser/json_parser'
# NOTE: Do not rely on this default value, as it may change
@options[:parser] = JSONParser.new
end
+ return @options[:parser]
+ end
+
+ ##
+ # Returns the authorization mechanism used by the client.
+ def authorization
unless @options[:authorization]
require 'signet/oauth_1/client'
# NOTE: Do not rely on this default value, as it may change
@@ -40,34 +56,126 @@ module Google #:nodoc:
:client_credential_secret => 'anonymous'
)
end
- unless @options[:http_adapter]
+ return @options[:authorization]
+ end
+
+ ##
+ # Returns the HTTP adapter used by the client.
+ def http_adapter
+ return @options[:http_adapter] ||= (begin
require 'httpadapter/adapters/net_http'
@options[:http_adapter] = HTTPAdapter::NetHTTPRequestAdapter
+ end)
+ end
+
+ def discovery_uri
+ return @options[:discovery_uri] ||= (begin
+ if @options[:service]
+ service_id = @options[:service]
+ service_version = @options[:service_version] || '1.0'
+ "http://www.googleapis.com/discovery/0.1/describe" +
+ "?api=#{service_id}&apiVersion=#{service_version}"
+ else
+ raise ArgumentError,
+ 'Missing required configuration value, :discovery_uri.'
+ end
+ end)
+ end
+
+ def discovery_document
+ return @discovery_document ||= (begin
+ request = ['GET', self.discovery_uri, [], []]
+ response = self.transmit_request(request)
+ status, headers, body = response
+ if status == 200
+ merged_body = StringIO.new
+ body.each do |chunk|
+ merged_body.write(chunk)
+ end
+ merged_body.rewind
+ JSON.parse(merged_body.string)
+ else
+ raise TransmissionError,
+ "Could not retrieve discovery document at: #{self.discovery_uri}"
+ end
+ end)
+ end
+
+ def discovered_services
+ return @discovered_services ||= (begin
+ service_names = self.discovery_document['data'].keys()
+ services = []
+ for service_name in service_names
+ versions = self.discovery_document['data'][service_name]
+ for version_name in versions.keys()
+ service_description =
+ self.discovery_document['data'][service_name][version_name]
+ service_version = "%.1f" % version_name.gsub(/^v/, '').to_f
+ services << ::Google::APIClient::Service.new(
+ service_name,
+ service_version,
+ service_description
+ )
+ end
+ end
+ services
+ end)
+ end
+
+ def discovered_service(service_name, service_version='1.0')
+ for service in self.discovered_services
+ if service.name == service_name &&
+ service.version.to_s == service_version.to_s
+ return service
+ end
end
- end
-
- ##
- # Returns the parser used by the client.
- def parser
- return @options[:parser]
- end
-
- ##
- # Returns the authorization mechanism used by the client.
- def authorization
- return @options[:authorization]
+ return nil
end
def build_request(*args, &block)
if !args.empty? || block
- # Build the request!
- # TODO(bobaman): No-op / Debug code!
+ if args.size != 2
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
+ end
+ rpc_name = args[0]
+ if !rpc_name.kind_of?(String)
+ raise TypeError, "Expected String, got #{rpc_name.class}."
+ end
+ parameters = args[1]
+ if !parameters.kind_of?(Hash)
+ raise TypeError, "Expected Hash, got #{parameters.class}."
+ end
+
+ signature_needed = false
+ if signature_needed
+ request = self.sign_request(request)
+ end
return args
else
require 'google/api_client/discovery/method_builder'
return ::Google::APIClient::MethodBuilder.new(self, :build_request)
end
end
+
+ def transmit_request(request)
+ ::HTTPAdapter.transmit(request, self.http_adapter)
+ end
+
+ def sign_request(request)
+ if self.authorization.respond_to?(:generate_authenticated_request)
+ return self.authorization.generate_authenticated_request(
+ :request => request
+ )
+ else
+ raise TypeError,
+ 'Expected authorization mechanism to respond to ' +
+ '#generate_authenticated_request.'
+ end
+ end
+
+ def execute_signed_request(*args, &block)
+ return self.transmit_request(self.build_request(*args, &block))
+ end
end
end
diff --git a/lib/google/api_client/discovery/method_builder.rb b/lib/google/api_client/discovery/method_builder.rb
deleted file mode 100644
index ed1f69ea7..000000000
--- a/lib/google/api_client/discovery/method_builder.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2010 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Define a BasicObject implementation if one doesn't already exist.
-unless defined?(BasicObject)
- class BasicObject
- instance_methods.each do |m|
- if m.to_s !~ /^(?:!=?|==|__.*__|equal\?|instance_(?:eval|exec))$/
- undef_method(m)
- end
- end
- end
-end
-
-module Google #:nodoc:
- class APIClient #:nodoc:
- ##
- # A builder class for assembling requests.
- class MethodBuilder < BasicObject
- ##
- # Creates a new {MethodBuilder}.
- #
- # @param [Google::APIClient] client
- # The client the {MethodBuilder} will use to build requests.
- def initialize(client, callback=:build_request)
- @segments = []
- @client = client
- @callback = callback
- end
-
- ##
- # Appends a segment to the builder.
- #
- # @param [String, Symbol, #to_s] segment The segment to append.
- def <<(segment)
- @segments << segment.to_s
- end
-
- ##
- # Returns the assembled segments. This maps to the rpcName
- # field in the discovery document.
- #
- # @return [String] The RPC name of the method.
- def to_s
- return @segments.join(".")
- end
- alias_method :to_str, :to_s
-
- ##
- # The call method will force the builder to finish assembling segments
- # and build the request.
- #
- # @return [Enumerable]
- def call(*args, &block)
- return @client.build_request(self.to_str, *args, &block)
- end
-
- ##
- # Any methods called on the {MethodBuilder} will cause segments to be
- # added to the list.
- #
- # @return [Enumerable, Google::APIClient::MethodBuilder]
- # Allows chaining methods to build an endpoint name. The request
- # is built when parameters are sent. If an endpoint does not take
- # parameters, the {#call} method may be used to terminate the
- # sequence.
- def method_missing(method, *args, &block)
- self << method
- if !args.empty? || block
- return @client.send(callback, self.to_str, *args, &block)
- else
- return self
- end
- end
- end
- end
-end
diff --git a/lib/google/api_client/discovery/service.rb b/lib/google/api_client/discovery/service.rb
new file mode 100644
index 000000000..0db554fb4
--- /dev/null
+++ b/lib/google/api_client/discovery/service.rb
@@ -0,0 +1,184 @@
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'json'
+require 'addressable/uri'
+require 'addressable/template'
+require 'extlib/inflection'
+
+module Google #:nodoc:
+ class APIClient #:nodoc:
+ class ValidationError < StandardError
+ end
+
+ class Service
+ def initialize(service_name, service_version, service_description)
+ @name = service_name
+ @version = service_version
+ @description = service_description
+ metaclass = (class <String representation of the service's state.
+ #
+ # @return [String] The service's state, as a String
.
+ def inspect
+ sprintf(
+ "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
+ )
+ end
+ end
+
+ class Resource
+ def initialize(base, resource_name, resource_description)
+ @base = base
+ @name = resource_name
+ @description = resource_description
+ metaclass = (class <String representation of the resource's state.
+ #
+ # @return [String] The resource's state, as a String
.
+ def inspect
+ sprintf(
+ "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
+ )
+ end
+ end
+
+ class Method
+ def initialize(base, method_name, method_description)
+ @base = base
+ @name = method_name
+ @description = method_description
+ end
+
+ attr_reader :name, :description, :base
+
+ def uri_template
+ return Addressable::Template.new(base + self.description['pathUrl'])
+ end
+
+ def generate_uri(parameters={})
+ # Convert keys to Strings when appropriate
+ parameters = parameters.inject({}) do |accu, (k, v)|
+ k = k.to_s if k.kind_of?(Symbol)
+ k = k.to_str if k.respond_to?(:to_str)
+ unless k.kind_of?(String)
+ raise TypeError, "Expected String, got #{k.class}."
+ end
+ accu[k] = v
+ accu
+ end
+ template_variables = self.uri_template.variables
+ missing_variables = template_variables - parameters.keys
+ if missing_variables.size > 0
+ raise ArgumentError,
+ "Missing required parameters: #{missing_variables.join(', ')}."
+ end
+ uri = self.uri_template.expand(parameters)
+ query_parameters = parameters.reject do |k, v|
+ template_variables.include?(k)
+ end
+ if query_parameters.size > 0
+ uri.query_values = (uri.query_values || {}).merge(query_parameters)
+ end
+ # Normalization is necessary because of undesirable percent-escaping
+ # during URI template expansion
+ return uri.normalize
+ end
+
+ ##
+ # Returns a String
representation of the method's state.
+ #
+ # @return [String] The method's state, as a String
.
+ def inspect
+ sprintf(
+ "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
+ )
+ end
+ end
+ end
+end