Merge pull request #243 from sqrrrl/master

0.9 - core library rewrite (pull request 1 of 2)
This commit is contained in:
Steve Bazyl 2015-06-23 16:13:02 -07:00
commit 986a47c30b
132 changed files with 29124 additions and 9292 deletions

2
.rspec
View File

@ -1,2 +1,2 @@
--colour --color
--format documentation --format documentation

7
.rubocop.yml Normal file
View File

@ -0,0 +1,7 @@
inherit_from: .rubocop_todo.yml
Metrics/LineLength:
Max: 120
Style/FormatString:
EnforcedStyle: sprintf

63
.rubocop_todo.yml Normal file
View File

@ -0,0 +1,63 @@
# This configuration was generated by `rubocop --auto-gen-config`
# on 2015-03-25 23:30:36 -0700 using RuboCop version 0.29.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 19
Metrics/AbcSize:
Max: 79
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 220
# Offense count: 5
Metrics/CyclomaticComplexity:
Max: 10
# Offense count: 99
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
Max: 127
# Offense count: 18
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 43
# Offense count: 1
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 6
# Offense count: 4
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 14
Style/Documentation:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyLines:
Enabled: false
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/EmptyLinesAroundClassBody:
Enabled: false
# Offense count: 2
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
Style/Next:
Enabled: false
# Offense count: 3
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false

View File

@ -3,15 +3,13 @@ rvm:
- 2.2 - 2.2
- 2.0.0 - 2.0.0
- 2.1 - 2.1
- 1.9.3 - jruby-9000
- rbx-2
- jruby
env: env:
- RAILS_VERSION="~>3.2" - RAILS_VERSION="~>3.2"
- RAILS_VERSION="~>4.0.0" - RAILS_VERSION="~>4.0.0"
- RAILS_VERSION="~>4.1.0" - RAILS_VERSION="~>4.1.0"
- RAILS_VERSION="~>4.2.0" - RAILS_VERSION="~>4.2.0"
script: "bundle exec rake spec:all" script: "rake spec:all"
before_install: before_install:
- sudo apt-get update - sudo apt-get update
- sudo apt-get install idn - sudo apt-get install idn

View File

@ -1,7 +1,12 @@
--markup markdown --hide-void-return
--no-private
--verbose
--markup-provider=redcarpet
--markup=markdown
lib/**/*.rb lib/**/*.rb
ext/**/*.c generated/**/*.rb
- -
README.md README.md
CHANGELOG.md MIGRATING.md
CONTRIBUTING.md
LICENSE LICENSE

View File

@ -1,3 +1,9 @@
# 0.9.0
* WARNING: Please see [MIGRATING](MIGRATING.md) for important information.
* API classes are now generated ahead of time instead of at runtime.
* Drop support for Ruby versions < 2.0
* Switch from Faraday to Hurley for HTTP client
# 0.8.6 # 0.8.6
* Use discovered 'rootUrl' as base URI for services * Use discovered 'rootUrl' as base URI for services
* Respect discovered methods with colons in path * Respect discovered methods with colons in path

View File

@ -29,4 +29,3 @@ accept your pull requests.
1. Ensure that your code is clear and comprehensible. 1. Ensure that your code is clear and comprehensible.
1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Ensure that your code has an appropriate set of unit tests which all pass.
1. Submit a pull request. 1. Submit a pull request.

28
Gemfile
View File

@ -1,8 +1,34 @@
source 'https://rubygems.org' source 'https://rubygems.org'
# Specify your gem's dependencies in google-apis.gemspec
gemspec gemspec
gem 'jruby-openssl', :platforms => :jruby
group :development do
gem 'bundler', '~> 1.7'
gem 'rake', '~> 10.0'
gem 'rspec', '~> 3.1'
gem 'json_spec', '~> 1.1'
gem 'webmock', '~> 1.21'
gem 'simplecov', '~> 0.9'
gem 'coveralls', '~> 0.7.11'
gem 'rubocop', '~> 0.29'
gem 'launchy', '~> 2.4'
end
platforms :jruby do
group :development do
gem 'jruby-openssl'
end
end
platforms :ruby do
group :development do
gem 'yard', '~> 0.8'
gem 'redcarpet', '~> 3.2'
gem 'github-markup', '~> 1.3'
end
end
if ENV['RAILS_VERSION'] if ENV['RAILS_VERSION']
gem 'rails', ENV['RAILS_VERSION'] gem 'rails', ENV['RAILS_VERSION']

171
MIGRATING.md Normal file
View File

@ -0,0 +1,171 @@
# Migrating from version `0.8.x` to `0.9`
Many changes and improvements have been made to the `google-api-ruby-client`
library to bring it to `0.9`. If you are starting a new project or haven't used
this library before version `0.9`, see the [README][readme] to get started
as you won't need to migrate anything.
Code written against the `0.8.x` version of this library will not work with the `0.9`
version without modification.
## Discovery
In `0.8.x` the library would "discover" APIs on the fly, introducing
additional network calls and instability. That has been fixed in `0.9`.
To get the `drive` client in `0.8.x` required this:
```ruby
require 'google/api_client'
client = Google::APIClient.new
drive = client.discovered_api('drive', 'v2')
```
In `0.9` the same thing can be accomplished like this:
```ruby
require 'google/apis/drive_v2'
drive = Google::Apis::DriveV2::DriveService.new
```
All APIs are immediately accessible without requiring additional network calls or runtime code generation.
## API Methods
The calling style for API methods has changed. In `0.8.x` all calls were via a generic `execute` method. In `0.9`
the generated services have fully defined method signatures for all available methods.
To get a file using the Google Drive API in `0.8.x` required this:
```ruby
file = client.execute(:api_method => drive.file.get, :parameters => { 'id' => 'abc123' })
```
In `0.9` the same thing can be accomplished like this:
```ruby
file = drive.get_file('abc123')
```
Full API definitions including available methods, parameters, and data classes can be found in the `generated` directory.
## Authorization
In the 0.9 version of this library, the authentication and authorization code was moved
to the new [googleauth](https://github.com/google/google-auth-library-ruby) library. While the new library provides
significantly simpler APIs for some use cases, not all features have been migrated. Missing features
are expected to be added by end of Q2 2015.
The underlying [Signet](https://github.com/google/signet) is still used for authorization. OAuth 2 credentials obtained
previously will continue to work with the `0.9` version. OAuth 1 is no longer supported.
## Media uploads
Media uploads are significantly simpler in `0.9`.
The old `0.8.x` way of uploading media:
```ruby
media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
metadata = {
'title' => 'My movie',
'description' => 'The best home movie ever made'
}
client.execute(:api_method => drive.files.insert,
:parameters => { 'uploadType' => 'multipart' },
:body_object => metadata,
:media => media )
```
The new way in `0.9` using `upload_source` and `content_type` parameters:
```ruby
metadata = {
title: 'My movie',
description: 'The best home movie ever made'
}
drive.insert_file(metadata, upload_source: 'mymovie.m4v', content_type: 'video/mp4')
```
`upload_source` can be either a path to a file, an `IO` stream, or a `StringIO` instance.
Uploads are resumable and will be automatically retried if interrupted.
## Media downloads
`0.9` introduces support for media downloads (`alt=media`). To download content, use the `download_dest` parameter:
```ruby
drive.get_file('abc123', download_dest: '/tmp/myfile.txt')
```
`download_dest` may be either a path to a file or an `IO` stream.
## Batch Requests
The old `0.8.x` way of performing batch requests:
```ruby
client = Google::APIClient.new
urlshortener = client.discovered_api('urlshortener')
batch = Google::APIClient::BatchRequest.new do |result|
puts result.data
end
batch.add(:api_method => urlshortener.url.insert,
:body_object => { 'longUrl' => 'http://example.com/foo' })
batch.add(:api_method => urlshortener.url.insert,
:body_object => { 'longUrl' => 'http://example.com/bar' })
client.execute(batch)
```
In `0.9`, the equivalent code is:
```ruby
require 'google/apis/urlshortner_v1'
urlshortener = Google::Apis::UrlshortenerV1::UrlshortenerService.new
urlshortener.batch do |urlshortener|
urlshortner.insert_url({long_url: 'http://example.com/foo'}) do |res, err|
puts res
end
urlshortner.insert_url({long_url: 'http://example.com/bar'}) do |res, err|
puts res
end
end
```
Or if sharing the same block:
```ruby
require 'google/apis/urlshortner_v1'
urlshortener = Google::Apis::UrlshortenerV1::UrlshortenerService.new
callback = lambda { |res, err| puts res }
urlshortener.batch do |urlshortener|
urlshortner.insert_url({long_url: 'http://example.com/foo'}, &callback)
urlshortner.insert_url({long_url: 'http://example.com/bar'}, &callback)
end
```
## JRuby
Jruby 1.7.4 in 2.0 compatibility mode is supported. To enable for a specific script:
```
jruby --2.0 myscript.rb
```
Or set as the default:
```
export JRUBY_OPTS=--2.0
```
JRuby 9000 will be supported once released.

376
README.md
View File

@ -1,218 +1,226 @@
# Google API Client # Google API Client
<dl>
<dt>Homepage</dt><dd><a href="http://www.github.com/google/google-api-ruby-client">http://www.github.com/google/google-api-ruby-client</a></dd>
<dt>Authors</dt><dd>Bob Aman, <a href="mailto:sbazyl@google.com">Steven Bazyl</a></dd>
<dt>Copyright</dt><dd>Copyright © 2011 Google, Inc.</dd>
<dt>License</dt><dd>Apache 2.0</dd>
</dl>
[![Build Status](https://secure.travis-ci.org/google/google-api-ruby-client.png)](http://travis-ci.org/google/google-api-ruby-client)
[![Dependency Status](https://gemnasium.com/google/google-api-ruby-client.png)](https://gemnasium.com/google/google-api-ruby-client)
## Description
The Google API Ruby Client makes it trivial to discover and access supported
APIs.
## Alpha ## Alpha
This library is in Alpha. We will make an effort to support the library, but we reserve the right to make incompatible changes when necessary. This library is in Alpha. We will make an effort to support the library, but we reserve the right to make incompatible
changes when necessary.
## Install ## Migrating from 0.8.x
Be sure `https://rubygems.org/` is in your gem sources. Version 0.9 is not compatible with previous versions. See [MIGRATING](MIGRATING.md) for additional details on how to
migrate to the latest version.
For normal client usage, this is sufficient: ## Installation
Add this line to your application's Gemfile:
```ruby
gem 'google-api-client'
```
And then execute:
$ bundle
Or install it yourself as:
```bash
$ gem install google-api-client $ gem install google-api-client
```
## Example Usage ## Usage
### Basic usage
To use an API, include the corresponding generated file and instantiate the service. For example to use the Drive API:
```ruby ```ruby
require 'google/api_client' require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/installed_app'
# Initialize the client. Drive = Google::Apis::DriveV2 # Alias the module
client = Google::APIClient.new( drive = Drive::DriveService.new
:application_name => 'Example Ruby application', drive.authorization = authorization # See Googleauth or Signet libraries
:application_version => '1.0.0'
)
# Initialize Google+ API. Note this will make a request to the # Search for files in Drive (first page only)
# discovery service every time, so be sure to use serialization files = drive.list_files(q: "title contains 'finances'")
# in your production code. Check the samples for more details. files.items.each do |file|
plus = client.discovered_api('plus') puts file.title
# Load client secrets from your client_secrets.json.
client_secrets = Google::APIClient::ClientSecrets.load
# Run installed application flow. Check the samples for a more
# complete example that saves the credentials between runs.
flow = Google::APIClient::InstalledAppFlow.new(
:client_id => client_secrets.client_id,
:client_secret => client_secrets.client_secret,
:scope => ['https://www.googleapis.com/auth/plus.me']
)
client.authorization = flow.authorize
# Make an API call.
result = client.execute(
:api_method => plus.activities.list,
:parameters => {'collection' => 'public', 'userId' => 'me'}
)
puts result.data
```
## API Features
### API Discovery
To take full advantage of the client, load API definitions prior to use. To load an API:
```ruby
urlshortener = client.discovered_api('urlshortener')
```
Specific versions of the API can be loaded as well:
```ruby
drive = client.discovered_api('drive', 'v2')
```
Locally cached discovery documents may be used as well. To load an API from a local file:
```ruby
# Output discovery document to JSON
File.open('my-api.json', 'w') do |f| f.puts MultiJson.dump(client.discovery_document('myapi', 'v1')) end
# Read discovery document and load API
doc = File.read('my-api.json')
client.register_discovery_document('myapi', 'v1', doc)
my_api = client.discovered_api('myapi', 'v1')
```
### Authorization
Most interactions with Google APIs require users to authorize applications via OAuth 2.0. The client library uses [Signet](https://github.com/google/signet) to handle most aspects of authorization. For additional details about Google's OAuth support, see [Google Developers](https://developers.google.com/accounts/docs/OAuth2).
Credentials can be managed at the connection level, as shown, or supplied on a per-request basis when calling `execute`.
For server-to-server interactions, like those between a web application and Google Cloud Storage, Prediction, or BigQuery APIs, use service accounts.
As of version 0.8.3, service accounts can be configured using
[Application Default Credentials][1], which rely on the credentials being
available in a well-known location. If the credentials are not present
and it's being used on a Compute Engine VM, it will use the VM's default credentials.
```ruby
client.authorization = :google_app_default # in a later version, this will become the default
client.authorization.fetch_access_token!
client.execute(...)
```
This is simpler API to use than in previous versions, although that is still available:
```ruby
key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret')
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/prediction',
:issuer => '123456-abcdef@developer.gserviceaccount.com',
:signing_key => key)
client.authorization.fetch_access_token!
client.execute(...)
```
Service accounts are also used for delegation in Google Apps domains. The target user for impersonation is specified by setting the `:person` parameter to the user's email address
in the credentials. Detailed instructions on how to enable delegation for your domain can be found at [developers.google.com](https://developers.google.com/drive/delegation).
### Automatic Retries & Backoff
The API client can automatically retry requests for recoverable errors. To enable retries, set the `client.retries` property to
the number of additional attempts. To avoid flooding servers, retries invovle a 1 second delay that increases on each subsequent retry.
In the case of authentication token expiry, the API client will attempt to refresh the token and retry the failed operation - this
is a specific exception to the retry rules.
The default value for retries is 0, but will be enabled by default in future releases.
### Batching Requests
Some Google APIs support batching requests into a single HTTP request. Use `Google::APIClient::BatchRequest`
to bundle multiple requests together.
Example:
```ruby
client = Google::APIClient.new
urlshortener = client.discovered_api('urlshortener')
batch = Google::APIClient::BatchRequest.new do |result|
puts result.data
end end
batch.add(:api_method => urlshortener.url.insert, # Upload a file
:body_object => { 'longUrl' => 'http://example.com/foo' }) metadata = Drive::File.new(title: 'My document')
batch.add(:api_method => urlshortener.url.insert, metadata = drive.insert_file(metadata, upload_source: 'test.txt', content_type: 'text/plain')
:body_object => { 'longUrl' => 'http://example.com/bar' })
client.execute(batch) # Download a file
drive.get_file(metadata.id, download_dest: '/tmp/myfile.txt')
``` ```
Blocks for handling responses can be specified either at the batch level or when adding an individual API call. For example: ### Media
Methods that allow media operations have additional parameters to specify the upload source or download destination.
For uploads, the `upload_source` parameter can be specified with either a path to a file, an `IO` stream, or `StringIO`
instance.
For downloads, the `download_dest` parameter can also be either a path to a file, an `IO` stream, or `StringIO` instance.
Both uploads & downloads are resumable. If an error occurs during transmission the request will be automatically
retried from the last received byte.
### Errors & Retries
Retries are disabled by default, but enabling retries is strongly encouraged. The number of retries can be configured
via `Google::Apis::RequestOptions`. Any number greater than 0 will enable retries.
To enable retries for all services:
```ruby ```ruby
batch.add(:api_method=>urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/bar' }) do |result| Google::Apis::RequestOptions.default.retries = 5
puts result.data ```
With retries enabled globally, retries can be disabled for specific calls by including a retry value of 0 in the
request options:
```ruby
drive.insert_file(metadata, upload_source: 'test.txt', content_type: 'text/plain', options: { retries: 0 })
```
When retries are enabled, if a server or rate limit error occurs during a request it is automatically retried with
an exponentially increasing delay on subsequent retries. If a request can not be retried or if the maximum number
of retries is exceeded, an exception is thrown.
### Callbacks
A block an be specified when making calls. If present, the block will be called with the result or error, rather than
returning the result from the call or raising the error. Example:
```ruby
# Search for files in Drive (first page only)
drive.list_files(q: "title contains 'finances'") do |res, err|
if err
# Handle error
else
# Handle response
end
end end
``` ```
### Media Upload This calling style is required when making batch requests as responses are not available until the entire batch
is complete.
### Batches
Multiple requests can be batched together into a single HTTP request to reduce overhead. Batched calls are executed
in parallel and the responses processed once all results are available
For APIs that support file uploads, use `Google::APIClient::UploadIO` to load the stream. Both multipart and resumable
uploads can be used. For example, to upload a file to Google Drive using multipart
```ruby ```ruby
drive = client.discovered_api('drive', 'v2') # Fetch a bunch of files by ID
ids = ['file_id_1', 'file_id_2', 'file_id_3', 'file_id_4']
media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4') drive.batch do |drive|
metadata = { ids.each do |id|
'title' => 'My movie', drive.get_file(id) do |res, err|
'description' => 'The best home movie ever made' # Handle response
} end
client.execute(:api_method => drive.files.insert, end
:parameters => { 'uploadType' => 'multipart' },
:body_object => metadata,
:media => media )
```
To use resumable uploads, change the `uploadType` parameter to `resumable`. To check the status of the upload
and continue if necessary, check `result.resumable_upload`.
```ruby
client.execute(:api_method => drive.files.insert,
:parameters => { 'uploadType' => 'resumable' },
:body_object => metadata,
:media => media )
upload = result.resumable_upload
# Resume if needed
if upload.resumable?
client.execute(upload)
end end
``` ```
## Samples Media operations -- uploads & downloads -- can not be included in batch with other requests.
See the full list of [samples on Github](https://github.com/google/google-api-ruby-client-samples). However, some APIs support batch uploads. To upload multiple files in a batch, use the `batch_upload` method instead.
Batch uploads should only be used when uploading multiple small files. For large files, upload files individually to
take advantage of the libraries built-in reusmable upload support.
### Hashes
While the API will always return instances of schema classes, plain hashes are accepted in method calls for
convenience. Hash keys must be symbols matching the attribute names on the corresponding object the hash is meant
to replace. For example:
```ruby
file = {id: '123', title: 'My document', labels: { starred: true }}
file = drive.insert_file(file) # Returns a Drive::File instance
```
is equivalent to:
```ruby
file = Drive::File.new(id: '123', title: 'My document')
file.labels = Drive::File::Labels.new(starred: true)
file = drive.update_file(file) # Returns a Drive::File instance
```
## Authorization
[OAuth 2](https://developers.google.com/accounts/docs/OAuth2) is used to authorize applications. This library uses
both [Signet](https://github.com/google/signet) and
[Google Auth Library for Ruby](https://github.com/google/google-auth-library-ruby) for OAuth 2 support.
The [Google Auth Library for Ruby](https://github.com/google/google-auth-library-ruby) provides an implementation of
[application default credentials] for Ruby. It offers a simple way to get authorization credentials for use in
calling Google APIs, best suited for cases when the call needs to have the same identity
and authorization level for the application independent of the user. This is
the recommended approach to authorize calls to Cloud APIs, particularly when
you're building an application that uses Google Compute Engine.
For per-user authorization, use [Signet](https://github.com/google/signet) to obtain user authorization.
### Passing authorization to requests
Authorization can be specified for the entire client, for an individual service instance, or on a per-request basis.
Set authorization for all service:
```ruby
Google::Apis::RequestOptions.default.authorization = authorization
# Services instantiated after this will inherit the authorization
```
On a per-service level:
```ruby
drive = Google::Apis::DriveV2::DriveService.new
drive.authorization = authorization
# All requests made with this service will use the same authorization
```
Per-request:
```ruby
drive.get_file('123', options: { authorization: authorization })
```
## Generating APIs
For [Cloud Endpoints](https://cloud.google.com/endpoints/) or other APIs not included in the gem, ruby code can be
generated from the discovery document.
To generate from a local discovery file:
$ generate-api gen <outdir> --file=<path>
A URL can also be specified:
$ generate-api gen <outdir> --url=<url>
## TODO
* ETag support (if-not-modified)
* Auto-paging results (maybe)
* Caching
* Model validations
## License
This library is licensed under Apache 2.0. Full license text is
available in [LICENSE.txt](LICENSE.txt).
## Contributing
See [CONTRIBUTING](contributing).
## Support ## Support
Please [report bugs at the project on Github](https://github.com/google/google-api-ruby-client/issues). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-api-ruby-client) about the client or APIs on [StackOverflow](http://stackoverflow.com). Please [report bugs at the project on Github](https://github.com/google/google-api-ruby-client/issues). Don't
hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-api-ruby-client) about the client or APIs
[1]: https://developers.google.com/accounts/docs/application-default-credentials on [StackOverflow](http://stackoverflow.com).

View File

@ -1,41 +1,2 @@
# -*- ruby -*- require "bundler/gem_tasks"
lib_dir = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'bundler/gem_tasks'
require 'rubygems'
require 'rake'
require File.join(File.dirname(__FILE__), 'lib/google/api_client', 'version')
PKG_DISPLAY_NAME = 'Google API Client'
PKG_NAME = PKG_DISPLAY_NAME.downcase.gsub(/\s/, '-')
PKG_VERSION = Google::APIClient::VERSION::STRING
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PKG_HOMEPAGE = 'https://github.com/google/google-api-ruby-client'
RELEASE_NAME = "REL #{PKG_VERSION}"
PKG_AUTHOR = ["Bob Aman", "Steve Bazyl"]
PKG_AUTHOR_EMAIL = "sbazyl@google.com"
PKG_SUMMARY = 'Package Summary'
PKG_DESCRIPTION = <<-TEXT
The Google API Ruby Client makes it trivial to discover and access supported
APIs.
TEXT
list = FileList[
'lib/**/*', 'spec/**/*', 'vendor/**/*',
'tasks/**/*', 'website/**/*',
'[A-Z]*', 'Rakefile'
].exclude(/[_\.]git$/)
(open(".gitignore") { |file| file.read }).split("\n").each do |pattern|
list.exclude(pattern)
end
PKG_FILES = list
task :default => 'spec'
WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false
SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])

871
api_names.yaml Normal file
View File

@ -0,0 +1,871 @@
---
"/adexchangebuyer:v1.3/PerformanceReport/latency50thPercentile": latency_50th_percentile
"/adexchangebuyer:v1.3/PerformanceReport/latency85thPercentile": latency_85th_percentile
"/adexchangebuyer:v1.3/PerformanceReport/latency95thPercentile": latency_95th_percentile
"/adexchangeseller:v2.0/adexchangeseller.accounts.adclients.list": list_account_ad_clients
"/adexchangeseller:v2.0/adexchangeseller.accounts.customchannels.get": get_account_custom_channel
"/adexchangeseller:v2.0/adexchangeseller.accounts.customchannels.list": list_account_custom_channels
"/adexchangeseller:v2.0/adexchangeseller.accounts.metadata.dimensions.list": list_account_metadata_dimensions
"/adexchangeseller:v2.0/adexchangeseller.accounts.metadata.metrics.list": list_account_metadata_metrics
"/adexchangeseller:v2.0/adexchangeseller.accounts.preferreddeals.get": get_account_preferred_deal
"/adexchangeseller:v2.0/adexchangeseller.accounts.preferreddeals.list": list_account_preferred_deals
"/adexchangeseller:v2.0/adexchangeseller.accounts.reports.saved.generate": generate_account_saved_report
"/adexchangeseller:v2.0/adexchangeseller.accounts.reports.saved.list": list_account_saved_reports
"/adexchangeseller:v2.0/adexchangeseller.accounts.urlchannels.list": list_account_url_channels
"/admin:directory_v1/directory.chromeosdevices.get": get_chrome_os_device
"/admin:directory_v1/directory.chromeosdevices.list": list_chrome_os_devices
"/admin:directory_v1/directory.chromeosdevices.patch": patch_chrome_os_device
"/admin:directory_v1/directory.chromeosdevices.update": update_chrome_os_device
"/admin:directory_v1/directory.groups.aliases.delete/alias": group_alias
"/admin:directory_v1/directory.mobiledevices.action": action_mobile_device
"/admin:directory_v1/directory.mobiledevices.delete": delete_mobile_device
"/admin:directory_v1/directory.mobiledevices.get": get_mobile_device
"/admin:directory_v1/directory.mobiledevices.list": list_mobile_devices
"/admin:directory_v1/directory.orgunits.delete": delete_org_unit
"/admin:directory_v1/directory.orgunits.get": get_org_unit
"/admin:directory_v1/directory.orgunits.insert": insert_org_unit
"/admin:directory_v1/directory.orgunits.list": list_org_units
"/admin:directory_v1/directory.orgunits.patch": patch_org_unit
"/admin:directory_v1/directory.orgunits.update": update_org_unit
"/admin:directory_v1/directory.users.aliases.delete/alias": user_alias
"/adsense:v1.4/AdsenseReportsGenerateResponse": generate_report_response
"/adsense:v1.4/adsense.accounts.adclients.list": list_account_ad_clients
"/adsense:v1.4/adsense.accounts.adunits.customchannels.list": list_account_ad_unit_custom_channels
"/adsense:v1.4/adsense.accounts.adunits.get": get_account_ad_unit
"/adsense:v1.4/adsense.accounts.adunits.getAdCode": get_account_ad_unit_ad_code
"/adsense:v1.4/adsense.accounts.adunits.list": list_account_ad_units
"/adsense:v1.4/adsense.accounts.customchannels.adunits.list": list_account_custom_channel_ad_units
"/adsense:v1.4/adsense.accounts.customchannels.get": get_account_custom_channel
"/adsense:v1.4/adsense.accounts.customchannels.list": list_account_custom_channels
"/adsense:v1.4/adsense.accounts.reports.saved.generate": generate_account_saved_report
"/adsense:v1.4/adsense.accounts.reports.saved.list": list_account_saved_reports
"/adsense:v1.4/adsense.accounts.savedadstyles.get": get_account_saved_ad_style
"/adsense:v1.4/adsense.accounts.savedadstyles.list": list_account_saved_ad_styles
"/adsense:v1.4/adsense.accounts.urlchannels.list": list_account_url_channels
"/adsense:v1.4/adsense.adclients.list": list_ad_clients
"/adsense:v1.4/adsense.adunits.customchannels.list": list_ad_unit_custom_channels
"/adsense:v1.4/adsense.adunits.get": get_ad_unit
"/adsense:v1.4/adsense.adunits.getAdCode": get_ad_code_ad_unit
"/adsense:v1.4/adsense.adunits.list": list_ad_units
"/adsense:v1.4/adsense.customchannels.adunits.list": list_custom_channel_ad_units
"/adsense:v1.4/adsense.customchannels.get": get_custom_channel
"/adsense:v1.4/adsense.customchannels.list": list_custom_channels
"/adsense:v1.4/adsense.metadata.dimensions.list": list_metadata_dimensions
"/adsense:v1.4/adsense.metadata.metrics.list": list_metadata_metrics
"/adsense:v1.4/adsense.reports.saved.generate": generate_saved_report
"/adsense:v1.4/adsense.reports.saved.list": list_saved_reports
"/adsense:v1.4/adsense.savedadstyles.get": get_saved_ad_style
"/adsense:v1.4/adsense.savedadstyles.list": list_saved_ad_styles
"/adsense:v1.4/adsense.urlchannels.list": list_url_channels
"/adsensehost:v4.1/adsensehost.accounts.adclients.get": get_account_ad_client
"/adsensehost:v4.1/adsensehost.accounts.adclients.list": list_account_ad_clients
"/adsensehost:v4.1/adsensehost.accounts.adunits.delete": delete_account_ad_unit
"/adsensehost:v4.1/adsensehost.accounts.adunits.get": get_account_ad_unit
"/adsensehost:v4.1/adsensehost.accounts.adunits.getAdCode": get_account_ad_unit_ad_code
"/adsensehost:v4.1/adsensehost.accounts.adunits.insert": insert_account_ad_unit
"/adsensehost:v4.1/adsensehost.accounts.adunits.list": list_account_ad_units
"/adsensehost:v4.1/adsensehost.accounts.adunits.patch": patch_account_ad_unit
"/adsensehost:v4.1/adsensehost.accounts.adunits.update": update_account_ad_unit
"/adsensehost:v4.1/adsensehost.adclients.get": get_ad_client
"/adsensehost:v4.1/adsensehost.adclients.list": list_ad_clients
"/adsensehost:v4.1/adsensehost.associationsessions.start": start_association_session
"/adsensehost:v4.1/adsensehost.associationsessions.verify": verify_association_session
"/adsensehost:v4.1/adsensehost.customchannels.delete": delete_custom_channel
"/adsensehost:v4.1/adsensehost.customchannels.get": get_custom_channel
"/adsensehost:v4.1/adsensehost.customchannels.insert": insert_custom_channel
"/adsensehost:v4.1/adsensehost.customchannels.list": list_custom_channels
"/adsensehost:v4.1/adsensehost.customchannels.patch": patch_custom_channel
"/adsensehost:v4.1/adsensehost.customchannels.update": update_custom_channel
"/adsensehost:v4.1/adsensehost.urlchannels.delete": delete_url_channel
"/adsensehost:v4.1/adsensehost.urlchannels.insert": insert_url_channel
"/adsensehost:v4.1/adsensehost.urlchannels.list": list_url_channels
"/analytics:v3/AnalyticsDataimportDeleteUploadDataRequest": delete_upload_data_request
"/analytics:v3/UnsampledReport/cloudStorageDownloadDetails/objectId": obj_id
"/analytics:v3/analytics.data.ga.get": get_ga_data
"/analytics:v3/analytics.data.mcf.get": get_mcf_data
"/analytics:v3/analytics.data.realtime.get": get_realtime_data
"/analytics:v3/analytics.management.accountSummaries.list": list_account_summaries
"/analytics:v3/analytics.management.accountUserLinks.delete": delete_account_user_link
"/analytics:v3/analytics.management.accountUserLinks.insert": insert_account_user_link
"/analytics:v3/analytics.management.accountUserLinks.list": list_account_user_links
"/analytics:v3/analytics.management.accountUserLinks.update": update_account_user_link
"/analytics:v3/analytics.management.accounts.list": list_accounts
"/analytics:v3/analytics.management.customDataSources.list": list_custom_data_sources
"/analytics:v3/analytics.management.customDimensions.get": get_custom_dimension
"/analytics:v3/analytics.management.customDimensions.insert": insert_custom_dimension
"/analytics:v3/analytics.management.customDimensions.list": list_custom_dimensions
"/analytics:v3/analytics.management.customDimensions.patch": patch_custom_dimension
"/analytics:v3/analytics.management.customDimensions.update": update_custom_dimension
"/analytics:v3/analytics.management.customMetrics.get": get_custom_metric
"/analytics:v3/analytics.management.customMetrics.insert": insert_custom_metric
"/analytics:v3/analytics.management.customMetrics.list": list_custom_metrics
"/analytics:v3/analytics.management.customMetrics.patch": patch_custom_metric
"/analytics:v3/analytics.management.customMetrics.update": update_custom_metric
"/analytics:v3/analytics.management.experiments.delete": delete_experiment
"/analytics:v3/analytics.management.experiments.get": get_experiment
"/analytics:v3/analytics.management.experiments.insert": insert_experiment
"/analytics:v3/analytics.management.experiments.list": list_experiments
"/analytics:v3/analytics.management.experiments.patch": patch_experiment
"/analytics:v3/analytics.management.experiments.update": update_experiment
"/analytics:v3/analytics.management.filters.delete": delete_filter
"/analytics:v3/analytics.management.filters.get": get_filter
"/analytics:v3/analytics.management.filters.insert": insert_filter
"/analytics:v3/analytics.management.filters.list": list_filters
"/analytics:v3/analytics.management.filters.patch": patch_filter
"/analytics:v3/analytics.management.filters.update": update_filter
"/analytics:v3/analytics.management.goals.get": get_goal
"/analytics:v3/analytics.management.goals.insert": insert_goal
"/analytics:v3/analytics.management.goals.list": list_goals
"/analytics:v3/analytics.management.goals.patch": patch_goal
"/analytics:v3/analytics.management.goals.update": update_goal
"/analytics:v3/analytics.management.profileFilterLinks.delete": delete_profile_filter_link
"/analytics:v3/analytics.management.profileFilterLinks.get": get_profile_filter_link
"/analytics:v3/analytics.management.profileFilterLinks.insert": insert_profile_filter_link
"/analytics:v3/analytics.management.profileFilterLinks.list": list_profile_filter_links
"/analytics:v3/analytics.management.profileFilterLinks.patch": patch_profile_filter_link
"/analytics:v3/analytics.management.profileFilterLinks.update": update_profile_filter_link
"/analytics:v3/analytics.management.profileUserLinks.delete": delete_profile_user_link
"/analytics:v3/analytics.management.profileUserLinks.insert": insert_profile_user_link
"/analytics:v3/analytics.management.profileUserLinks.list": list_profile_user_links
"/analytics:v3/analytics.management.profileUserLinks.update": update_profile_user_link
"/analytics:v3/analytics.management.profiles.delete": delete_profile
"/analytics:v3/analytics.management.profiles.get": get_profile
"/analytics:v3/analytics.management.profiles.insert": insert_profile
"/analytics:v3/analytics.management.profiles.list": list_profiles
"/analytics:v3/analytics.management.profiles.patch": patch_profile
"/analytics:v3/analytics.management.profiles.update": update_profile
"/analytics:v3/analytics.management.segments.list": list_segments
"/analytics:v3/analytics.management.unsampledReports.get": get_unsampled_report
"/analytics:v3/analytics.management.unsampledReports.insert": insert_unsampled_report
"/analytics:v3/analytics.management.unsampledReports.list": list_unsampled_reports
"/analytics:v3/analytics.management.uploads.deleteUploadData": delete_upload_data
"/analytics:v3/analytics.management.uploads.get": get_upload
"/analytics:v3/analytics.management.uploads.list": list_uploads
"/analytics:v3/analytics.management.uploads.uploadData": upload_data
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.delete": delete_web_property_ad_words_link
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.get": get_web_property_ad_words_link
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.insert": insert_web_property_ad_words_link
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.list": list_web_property_ad_words_links
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.patch": patch_web_property_ad_words_link
"/analytics:v3/analytics.management.webPropertyAdWordsLinks.update": update_web_property_ad_words_link
"/analytics:v3/analytics.management.webproperties.get": get_web_property
"/analytics:v3/analytics.management.webproperties.insert": insert_web_property
"/analytics:v3/analytics.management.webproperties.list": list_web_properties
"/analytics:v3/analytics.management.webproperties.patch": patch_web_property
"/analytics:v3/analytics.management.webproperties.update": update_web_property
"/analytics:v3/analytics.management.webpropertyUserLinks.delete": delete_web_property_user_link
"/analytics:v3/analytics.management.webpropertyUserLinks.insert": insert_web_property_user_link
"/analytics:v3/analytics.management.webpropertyUserLinks.list": list_web_property_user_links
"/analytics:v3/analytics.management.webpropertyUserLinks.update": update_web_property_user_link
"/analytics:v3/analytics.metadata.columns.list": list_metadata_columns
"/analytics:v3/analytics.provisioning.createAccountTicket": create_account_ticket
"/androidenterprise:v1/CollectionViewersListResponse": list_collection_viewers_response
"/androidenterprise:v1/CollectionsListResponse": list_collections_response
"/androidenterprise:v1/DevicesListResponse": list_devices_response
"/androidenterprise:v1/EnterprisesListResponse": list_enterprises_response
"/androidenterprise:v1/EntitlementsListResponse": list_entitlements_response
"/androidenterprise:v1/GroupLicenseUsersListResponse": list_group_license_users_response
"/androidenterprise:v1/GroupLicensesListResponse": list_group_licenses_response
"/androidenterprise:v1/InstallsListResponse": list_installs_response
"/androidenterprise:v1/UsersListResponse": list_users_response
"/androidenterprise:v1/androidenterprise.collectionviewers.delete": delete_collection_viewer
"/androidenterprise:v1/androidenterprise.collectionviewers.get": get_collection_viewer
"/androidenterprise:v1/androidenterprise.collectionviewers.list": list_collection_viewers
"/androidenterprise:v1/androidenterprise.collectionviewers.patch": patch_collection_viewer
"/androidenterprise:v1/androidenterprise.collectionviewers.update": update_collection_viewer
"/androidenterprise:v1/androidenterprise.grouplicenses.get": get_group_license
"/androidenterprise:v1/androidenterprise.grouplicenses.list": list_group_licenses
"/androidenterprise:v1/androidenterprise.grouplicenseusers.list": list_group_license_users
"/androidenterprise:v1/androidenterprise.products.generateApprovalUrl": generate_product_approval_url
"/androidenterprise:v1/androidenterprise.products.getAppRestrictionsSchema": get_product_app_restrictions_schema
"/androidenterprise:v1/androidenterprise.products.getPermissions": get_product_permissions
"/androidenterprise:v1/androidenterprise.products.updatePermissions": update_product_permissions
"/androidenterprise:v1/androidenterprise.users.generateToken": generate_user_token
"/androidenterprise:v1/androidenterprise.users.revokeToken": revoke_user_token
"/androidenterprise:v1/ProductsGenerateApprovalUrlResponse": generate_product_approval_url_response
"/androidenterprise:v1/ProductsApproveRequest": approve_product_request
"/androidpublisher:v2/ApkListingsListResponse": list_apk_listings_response
"/androidpublisher:v2/ApksAddExternallyHostedRequest": apks_add_externally_hosted_request
"/androidpublisher:v2/ApksAddExternallyHostedResponse": apks_add_externally_hosted_response
"/androidpublisher:v2/ApksListResponse": list_apks_response
"/androidpublisher:v2/EntitlementsListResponse": list_entitlements_response
"/androidpublisher:v2/ExpansionFilesUploadResponse": upload_expansion_files_response
"/androidpublisher:v2/ImagesDeleteAllResponse": images_delete_all_response
"/androidpublisher:v2/ImagesListResponse": list_images_response
"/androidpublisher:v2/ImagesUploadResponse": upload_images_response
"/androidpublisher:v2/InappproductsBatchRequest": in_app_products_batch_request
"/androidpublisher:v2/InappproductsBatchRequestEntry": in_app_products_batch_request_entry
"/androidpublisher:v2/InappproductsBatchResponse": in_app_products_batch_response
"/androidpublisher:v2/InappproductsBatchResponseEntry": in_app_products_batch_response_entry
"/androidpublisher:v2/InappproductsInsertRequest": insert_in_app_products_request
"/androidpublisher:v2/InappproductsInsertResponse": insert_in_app_products_response
"/androidpublisher:v2/InappproductsListResponse": list_in_app_products_response
"/androidpublisher:v2/InappproductsUpdateRequest": update_in_app_products_request
"/androidpublisher:v2/InappproductsUpdateResponse": update_in_app_products_response
"/androidpublisher:v2/ListingsListResponse": list_listings_response
"/androidpublisher:v2/SubscriptionPurchasesDeferRequest": defer_subscription_purchases_request
"/androidpublisher:v2/SubscriptionPurchasesDeferResponse": defer_subscription_purchases_response
"/androidpublisher:v2/TracksListResponse": list_tracks_response
"/androidpublisher:v2/androidpublisher.edits.apklistings.delete": delete_apk_listing
"/androidpublisher:v2/androidpublisher.edits.apklistings.deleteall": delete_all_apk_listings
"/androidpublisher:v2/androidpublisher.edits.apklistings.get": get_apk_listing
"/androidpublisher:v2/androidpublisher.edits.apklistings.list": list_apk_listings
"/androidpublisher:v2/androidpublisher.edits.apklistings.patch": patch_apk_listing
"/androidpublisher:v2/androidpublisher.edits.apklistings.update": update_apk_listing
"/androidpublisher:v2/androidpublisher.edits.apks.addexternallyhosted": add_externally_hosted_apk
"/androidpublisher:v2/androidpublisher.edits.apks.list": list_apks
"/androidpublisher:v2/androidpublisher.edits.apks.upload": upload_apk
"/androidpublisher:v2/androidpublisher.edits.details.get": get_detail
"/androidpublisher:v2/androidpublisher.edits.details.patch": patch_detail
"/androidpublisher:v2/androidpublisher.edits.details.update": update_detail
"/androidpublisher:v2/androidpublisher.edits.expansionfiles.get": get_expansion_file
"/androidpublisher:v2/androidpublisher.edits.expansionfiles.patch": patch_expansion_file
"/androidpublisher:v2/androidpublisher.edits.expansionfiles.update": update_expansion_file
"/androidpublisher:v2/androidpublisher.edits.expansionfiles.upload": upload_expansion_file
"/androidpublisher:v2/androidpublisher.edits.images.delete": delete_image
"/androidpublisher:v2/androidpublisher.edits.images.deleteall": delete_all_images
"/androidpublisher:v2/androidpublisher.edits.images.list": list_images
"/androidpublisher:v2/androidpublisher.edits.images.upload": upload_image
"/androidpublisher:v2/androidpublisher.edits.listings.delete": delete_listing
"/androidpublisher:v2/androidpublisher.edits.listings.deleteall": delete_all_listings
"/androidpublisher:v2/androidpublisher.edits.listings.get": get_listing
"/androidpublisher:v2/androidpublisher.edits.listings.list": list_listings
"/androidpublisher:v2/androidpublisher.edits.listings.patch": patch_listing
"/androidpublisher:v2/androidpublisher.edits.listings.update": update_listing
"/androidpublisher:v2/androidpublisher.edits.testers.get": get_tester
"/androidpublisher:v2/androidpublisher.edits.testers.patch": patch_tester
"/androidpublisher:v2/androidpublisher.edits.testers.update": update_tester
"/androidpublisher:v2/androidpublisher.edits.tracks.get": get_track
"/androidpublisher:v2/androidpublisher.edits.tracks.list": list_tracks
"/androidpublisher:v2/androidpublisher.edits.tracks.patch": patch_track
"/androidpublisher:v2/androidpublisher.edits.tracks.update": update_track
"/androidpublisher:v2/androidpublisher.entitlements.list": list_entitlements
"/androidpublisher:v2/androidpublisher.inappproducts.batch": batch_update_in_app_products
"/androidpublisher:v2/androidpublisher.inappproducts.delete": delete_in_app_product
"/androidpublisher:v2/androidpublisher.inappproducts.get": get_in_app_product
"/androidpublisher:v2/androidpublisher.inappproducts.insert": insert_in_app_product
"/androidpublisher:v2/androidpublisher.inappproducts.list": list_in_app_products
"/androidpublisher:v2/androidpublisher.inappproducts.patch": patch_in_app_product
"/androidpublisher:v2/androidpublisher.inappproducts.update": update_in_app_product
"/androidpublisher:v2/androidpublisher.purchases.products.get": get_purchase_product
"/androidpublisher:v2/androidpublisher.purchases.subscriptions.cancel": cancel_purchase_subscription
"/androidpublisher:v2/androidpublisher.purchases.subscriptions.defer": defer_purchase_subscription
"/androidpublisher:v2/androidpublisher.purchases.subscriptions.get": get_purchase_subscription
"/androidpublisher:v2/androidpublisher.purchases.subscriptions.refund": refund_purchase_subscription
"/androidpublisher:v2/androidpublisher.purchases.subscriptions.revoke": revoke_purchase_subscription
"/autoscaler:v1beta2/AutoscalerListResponse": list_autoscaler_response
"/bigquery:v2/TableDataInsertAllRequest": insert_all_table_data_request
"/bigquery:v2/TableDataInsertAllResponse": insert_all_table_data_response
"/bigquery:v2/bigquery.jobs.getQueryResults": get_job_query_results
"/bigquery:v2/bigquery.tabledata.insertAll": insert_all_table_data
"/bigquery:v2/bigquery.tabledata.list": list_table_data
"/bigquery:v2/JobCancelResponse": cancel_job_response
"/blogger:v3/blogger.blogs.getByUrl": get_blog_by_url
"/blogger:v3/blogger.blogs.listByUser": list_blogs_by_user
"/blogger:v3/blogger.comments.listByBlog": list_comments_by_blog
"/blogger:v3/blogger.comments.markAsSpam": mark_comment_as_spam
"/blogger:v3/blogger.comments.removeContent": remove_comment_content
"/blogger:v3/blogger.postUserInfos.get": get_post_user_info
"/blogger:v3/blogger.postUserInfos.list": list_post_user_info
"/blogger:v3/blogger.posts.getByPath": get_post_by_path
"/books:v1/Annotationdata": annotation_data
"/books:v1/AnnotationsSummary": annotations_summary
"/books:v1/Annotationsdata": annotations_data
"/books:v1/BooksAnnotationsRange": annotatins_Range
"/books:v1/BooksCloudloadingResource": loading_resource
"/books:v1/BooksVolumesRecommendedRateResponse": rate_recommended_volume_response
"/books:v1/Dictlayerdata": dict_layer_data
"/books:v1/Geolayerdata": geo_layer_data
"/books:v1/Layersummaries": layer_summaries
"/books:v1/Layersummary": layer_summary
"/books:v1/Usersettings": user_settings
"/books:v1/Volumeannotation": volume_annotation
"/books:v1/books.bookshelves.get": get_bookshelf
"/books:v1/books.bookshelves.list": list_bookshelves
"/books:v1/books.bookshelves.volumes.list": list_bookshelf_volumes
"/books:v1/books.cloudloading.addBook": add_book
"/books:v1/books.cloudloading.deleteBook": delete_book
"/books:v1/books.cloudloading.updateBook": update_book
"/books:v1/books.dictionary.listOfflineMetadata": list_offline_metadata_dictionary
"/books:v1/books.layers.annotationData.get": get_layer_annotation_data
"/books:v1/books.layers.annotationData.list": list_layer_annotation_data
"/books:v1/books.layers.get": get_layer
"/books:v1/books.layers.list": list_layers
"/books:v1/books.layers.volumeAnnotations.get": get_layer_volume_annotation
"/books:v1/books.layers.volumeAnnotations.list": list_layer_volume_annotations
"/books:v1/books.myconfig.getUserSettings": get_user_settings
"/books:v1/books.myconfig.releaseDownloadAccess": release_download_access
"/books:v1/books.myconfig.requestAccess": request_access
"/books:v1/books.myconfig.syncVolumeLicenses": sync_volume_licenses
"/books:v1/books.myconfig.updateUserSettings": update_user_settings
"/books:v1/books.mylibrary.annotations.delete": delete_my_library_annotation
"/books:v1/books.mylibrary.annotations.insert": insert_my_library_annotation
"/books:v1/books.mylibrary.annotations.list": list_my_library_annotations
"/books:v1/books.mylibrary.annotations.summary": summarize_my_library_annotation
"/books:v1/books.mylibrary.annotations.update": update_my_library_annotation
"/books:v1/books.mylibrary.bookshelves.addVolume": add_my_library_volume
"/books:v1/books.mylibrary.bookshelves.clearVolumes": clear_my_library_volumes
"/books:v1/books.mylibrary.bookshelves.get": get_my_library_bookshelf
"/books:v1/books.mylibrary.bookshelves.list": list_my_library_bookshelves
"/books:v1/books.mylibrary.bookshelves.moveVolume": move_my_library_volume
"/books:v1/books.mylibrary.bookshelves.removeVolume": remove_my_library_volume
"/books:v1/books.mylibrary.bookshelves.volumes.list": list_my_library_volumes
"/books:v1/books.mylibrary.readingpositions.get": get_my_library_reading_position
"/books:v1/books.mylibrary.readingpositions.setPosition": set_my_library_reading_position
"/books:v1/books.onboarding.listCategories": list_onboarding_categories
"/books:v1/books.onboarding.listCategoryVolumes": list_onboarding_category_volumes
"/books:v1/books.promooffer.accept": accept_promo_offer
"/books:v1/books.promooffer.dismiss": dismiss_promo_offer
"/books:v1/books.promooffer.get": get_promo_offer
"/books:v1/books.volumes.associated.list": list_associated_volumes
"/books:v1/books.volumes.mybooks.list": list_my_books
"/books:v1/books.volumes.recommended.list": list_recommended_volumes
"/books:v1/books.volumes.recommended.rate": rate_recommended_volume
"/books:v1/books.volumes.useruploaded.list": list_user_uploaded_volumes
"/calendar:v3/CalendarNotification/method": delivery_method
"/calendar:v3/Event/gadget/display": display_mode
"/calendar:v3/EventReminder/method": reminder_method
"/civicinfo:v2/DivisionSearchResponse": search_division_response
"/civicinfo:v2/ElectionsQueryResponse": query_elections_response
"/civicinfo:v2/civicinfo.divisions.search": search_divisions
"/civicinfo:v2/civicinfo.elections.electionQuery": query_election
"/civicinfo:v2/civicinfo.elections.voterInfoQuery": query_voter_info
"/civicinfo:v2/civicinfo.representatives.representativeInfoByAddress": representative_info_by_address
"/civicinfo:v2/civicinfo.representatives.representativeInfoByDivision": representative_info_by_division
"/compute:v1/DiskMoveRequest": move_disk_request
"/compute:v1/InstanceMoveRequest": move_instance_request
"/compute:v1/TargetPoolsAddHealthCheckRequest": add_target_pools_health_check_request
"/compute:v1/TargetPoolsAddInstanceRequest": add_target_pools_instance_request
"/compute:v1/TargetPoolsRemoveHealthCheckRequest": remove_target_pools_health_check_request
"/compute:v1/TargetPoolsRemoveInstanceRequest": remove_target_pools_instance_request
"/compute:v1/UrlMapsValidateRequest": validate_url_maps_request
"/compute:v1/UrlMapsValidateResponse": validate_url_maps_response
"/compute:v1/compute.addresses.aggregatedList": list_aggregated_addresses
"/compute:v1/compute.backendServices.getHealth": get_backend_service_health
"/compute:v1/compute.diskTypes.aggregatedList": list_aggregated_disk_types
"/compute:v1/compute.disks.aggregatedList": list_aggregated_disk
"/compute:v1/compute.disks.createSnapshot": create_disk_snapshot
"/compute:v1/compute.forwardingRules.aggregatedList": list_aggregated_forwarding_rules
"/compute:v1/compute.forwardingRules.setTarget": set_forwarding_rule_target
"/compute:v1/compute.globalForwardingRules.setTarget": set_global_forwarding_rule_target
"/compute:v1/compute.globalOperations.aggregatedList": list_aggregated_global_operation
"/compute:v1/compute.instances.addAccessConfig": add_instance_access_config
"/compute:v1/compute.instances.aggregatedList": list_aggregated_instances
"/compute:v1/compute.instances.attachDisk": attach_disk
"/compute:v1/compute.instances.deleteAccessConfig": delete_instance_access_config
"/compute:v1/compute.instances.detachDisk": detach_disk
"/compute:v1/compute.instances.getSerialPortOutput": get_instance_serial_port_output
"/compute:v1/compute.instances.setDiskAutoDelete": set_disk_auto_delete
"/compute:v1/compute.instances.setMetadata": set_instance_metadata
"/compute:v1/compute.instances.setScheduling": set_instance_scheduling
"/compute:v1/compute.instances.setTags": set_instance_tags
"/compute:v1/compute.machineTypes.aggregatedList": list_aggregated_machine_types
"/compute:v1/compute.projects.moveDisk": move_disk
"/compute:v1/compute.projects.moveInstance": move_instance
"/compute:v1/compute.projects.setCommonInstanceMetadata": set_common_instance_metadata
"/compute:v1/compute.projects.setUsageExportBucket": set_usage_export_bucket
"/compute:v1/compute.targetHttpProxies.setUrlMap": set_target_http_proxy_url_map
"/compute:v1/compute.targetInstances.aggregatedList": list_aggregated_target_instance
"/compute:v1/compute.targetPools.addHealthCheck": add_target_pool_health_check
"/compute:v1/compute.targetPools.addInstance": add_target_pool_instance
"/compute:v1/compute.targetPools.aggregatedList": list_aggregated_target_pools
"/compute:v1/compute.targetPools.getHealth": get_target_pool_health
"/compute:v1/compute.targetPools.removeHealthCheck": remove_target_pool_health_check
"/compute:v1/compute.targetPools.removeInstance": remove_target_pool_instance
"/compute:v1/compute.targetPools.setBackup": set_target_pool_backup
"/compute:v1/compute.targetVpnGateways.aggregatedList": list_aggregated_target_vpn_gateways
"/compute:v1/compute.targetVpnGateways.delete": delete_target_vpn_gateway
"/compute:v1/compute.targetVpnGateways.get": get_target_vpn_gateway
"/compute:v1/compute.targetVpnGateways.insert": insert_target_vpn_gateway
"/compute:v1/compute.targetVpnGateways.list": list_target_vpn_gateways
"/compute:v1/compute.vpnTunnels.aggregatedList": list_aggregated_vpn_tunnel
"/container:v1beta1/container.projects.clusters.list": list_clusters
"/container:v1beta1/container.projects.operations.list": list_operations
"/container:v1beta1/container.projects.zones.clusters.create": create_cluster
"/container:v1beta1/container.projects.zones.clusters.delete": delete_zone_cluster
"/container:v1beta1/container.projects.zones.clusters.get": get_zone_cluster
"/container:v1beta1/container.projects.zones.clusters.list": list_zone_clusters
"/container:v1beta1/container.projects.zones.operations.get": get_zone_operation
"/container:v1beta1/container.projects.zones.operations.list": list_zone_operations
"/container:v1beta1/container.projects.zones.tokens.get": get_zone_token
"/content:v2/AccountsAuthInfoResponse": accounts_auth_info_response
"/content:v2/AccountsCustomBatchRequest": accounts_custom_batch_request
"/content:v2/AccountsCustomBatchRequest": batch_accounts_request
"/content:v2/AccountsCustomBatchRequestEntry": accounts_batch_request_entry
"/content:v2/AccountsCustomBatchRequestEntry/method": request_method
"/content:v2/AccountsCustomBatchResponse": batch_accounts_response
"/content:v2/AccountsCustomBatchResponse": batch_accounts_response
"/content:v2/AccountsCustomBatchResponseEntry": accounts_batch_response_entry
"/content:v2/AccountsListResponse": list_accounts_response
"/content:v2/AccountshippingCustomBatchRequest": batch_account_shipping_request
"/content:v2/AccountshippingCustomBatchRequest": batch_account_shipping_request
"/content:v2/AccountshippingCustomBatchRequestEntry": account_shipping_batch_request_entry
"/content:v2/AccountshippingCustomBatchRequestEntry/method": request_method
"/content:v2/AccountshippingCustomBatchResponse": batch_account_shipping_response
"/content:v2/AccountshippingCustomBatchResponse": batch_account_shipping_response
"/content:v2/AccountshippingCustomBatchResponseEntry": account_shipping_batch_response_entry
"/content:v2/AccountshippingListResponse": list_account_shipping_response
"/content:v2/AccountstatusesCustomBatchRequest": batch_account_statuses_request
"/content:v2/AccountstatusesCustomBatchRequest": batch_account_statuses_request
"/content:v2/AccountstatusesCustomBatchRequestEntry": account_statuses_batch_request_entry
"/content:v2/AccountstatusesCustomBatchRequestEntry/method": request_method
"/content:v2/AccountstatusesCustomBatchResponse": batch_account_statuses_response
"/content:v2/AccountstatusesCustomBatchResponse": batch_account_statuses_response
"/content:v2/AccountstatusesCustomBatchResponseEntry": account_statuses_batch_response_entry
"/content:v2/AccountstatusesListResponse": list_account_statuses_response
"/content:v2/AccounttaxCustomBatchRequest": batch_account_tax_request
"/content:v2/AccounttaxCustomBatchRequest": batch_account_tax_request
"/content:v2/AccounttaxCustomBatchRequestEntry": account_tax_batch_request_entry
"/content:v2/AccounttaxCustomBatchRequestEntry/method": request_method
"/content:v2/AccounttaxCustomBatchResponse": batch_account_tax_response
"/content:v2/AccounttaxCustomBatchResponse": batch_account_tax_response
"/content:v2/AccounttaxCustomBatchResponseEntry": account_tax_batch_response_entry
"/content:v2/AccounttaxListResponse": list_account_tax_response
"/content:v2/DatafeedsCustomBatchRequest": batch_datafeeds_request
"/content:v2/DatafeedsCustomBatchRequest": batch_datafeeds_request
"/content:v2/DatafeedsCustomBatchRequestEntry": datafeeds_batch_request_entry
"/content:v2/DatafeedsCustomBatchRequestEntry/method": request_method
"/content:v2/DatafeedsCustomBatchResponse": batch_datafeeds_response
"/content:v2/DatafeedsCustomBatchResponse": batch_datafeeds_response
"/content:v2/DatafeedsCustomBatchResponseEntry": datafeeds_batch_response_entry
"/content:v2/DatafeedsListResponse": list_datafeeds_response
"/content:v2/DatafeedstatusesCustomBatchRequest": batch_datafeed_statuses_request
"/content:v2/DatafeedstatusesCustomBatchRequest": batch_datafeed_statuses_request
"/content:v2/DatafeedstatusesCustomBatchRequestEntry": datafeed_statuses_batch_request_entry
"/content:v2/DatafeedstatusesCustomBatchRequestEntry/method": request_method
"/content:v2/DatafeedstatusesCustomBatchResponse": batch_datafeed_statuses_response
"/content:v2/DatafeedstatusesCustomBatchResponse": batch_datafeed_statuses_response
"/content:v2/DatafeedstatusesCustomBatchResponseEntry": datafeed_statuses_batch_response_entry
"/content:v2/DatafeedstatusesListResponse": list_datafeed_statuses_response
"/content:v2/InventoryCustomBatchRequest": batch_inventory_request
"/content:v2/InventoryCustomBatchRequest": batch_inventory_request
"/content:v2/InventoryCustomBatchRequestEntry": inventory_batch_request_entry
"/content:v2/InventoryCustomBatchResponse": batch_inventory_response
"/content:v2/InventoryCustomBatchResponse": batch_inventory_response
"/content:v2/InventoryCustomBatchResponseEntry": inventory_batch_response_entry
"/content:v2/InventorySetRequest": set_inventory_request
"/content:v2/InventorySetResponse": set_inventory_response
"/content:v2/ProductsCustomBatchRequest": batch_products_request
"/content:v2/ProductsCustomBatchRequest": batch_products_request
"/content:v2/ProductsCustomBatchRequestEntry": products_batch_request_entry
"/content:v2/ProductsCustomBatchRequestEntry/method": request_method
"/content:v2/ProductsCustomBatchResponse": batch_products_response
"/content:v2/ProductsCustomBatchResponse": batch_products_response
"/content:v2/ProductsCustomBatchResponseEntry": products_batch_response_entry
"/content:v2/ProductsListResponse": list_products_response
"/content:v2/ProductstatusesCustomBatchRequest": batch_product_statuses_request
"/content:v2/ProductstatusesCustomBatchRequest": batch_product_statuses_request
"/content:v2/ProductstatusesCustomBatchRequestEntry": product_statuses_batch_request_entry
"/content:v2/ProductstatusesCustomBatchRequestEntry/method": request_method
"/content:v2/ProductstatusesCustomBatchResponse": batch_product_statuses_response
"/content:v2/ProductstatusesCustomBatchResponse": batch_product_statuses_response
"/content:v2/ProductstatusesCustomBatchResponseEntry": product_statuses_batch_response_entry
"/content:v2/ProductstatusesListResponse": list_product_statuses_response
"/content:v2/content.accounts.authinfo": get_account_authinfo
"/content:v2/content.accounts.custombatch": batch_account
"/content:v2/content.accountshipping.custombatch": batch_account_shipping
"/content:v2/content.accountshipping.get": get_account_shipping
"/content:v2/content.accountshipping.list": list_account_shippings
"/content:v2/content.accountshipping.patch": patch_account_shipping
"/content:v2/content.accountshipping.update": update_account_shipping
"/content:v2/content.accountstatuses.custombatch": batch_account_status
"/content:v2/content.accountstatuses.get": get_account_status
"/content:v2/content.accountstatuses.list": list_account_statuses
"/content:v2/content.accounttax.custombatch": batch_account_tax
"/content:v2/content.accounttax.get": get_account_tax
"/content:v2/content.accounttax.list": list_account_taxes
"/content:v2/content.accounttax.patch": patch_account_tax
"/content:v2/content.accounttax.update": update_account_tax
"/content:v2/content.datafeeds.custombatch": batch_datafeed
"/content:v2/content.datafeedstatuses.custombatch": batch_datafeed_status
"/content:v2/content.datafeedstatuses.get": get_datafeed_status
"/content:v2/content.datafeedstatuses.list": list_datafeed_statuses
"/content:v2/content.inventory.custombatch": batch_inventory
"/content:v2/content.inventory.set": set_inventory
"/content:v2/content.products.custombatch": batch_product
"/content:v2/content.productstatuses.custombatch": batch_product_status
"/content:v2/content.productstatuses.get": get_product_status
"/content:v2/content.productstatuses.list": list_product_statuses
"/coordinate:v1/CustomFieldDefListResponse": list_custom_field_def_response
"/coordinate:v1/JobListResponse": list_job_response
"/coordinate:v1/LocationListResponse": list_location_response
"/coordinate:v1/TeamListResponse": list_team_response
"/coordinate:v1/WorkerListResponse": list_worker_response
"/datastore:v1beta2/AllocateIdsRequest": allocate_ids_request
"/datastore:v1beta2/AllocateIdsResponse": allocate_ids_response
"/datastore:v1beta2/BeginTransactionRequest": begin_transaction_request
"/datastore:v1beta2/BeginTransactionResponse": begin_transaction_response
"/deploymentmanager:v2beta1/DeploymentsListResponse": list_deployments_response
"/deploymentmanager:v2beta1/ManifestsListResponse": list_manifests_response
"/deploymentmanager:v2beta1/OperationsListResponse": list_operations_response
"/deploymentmanager:v2beta1/ResourcesListResponse": list_resources_response
"/deploymentmanager:v2beta1/TypesListResponse": list_types_response
"/deploymentmanager:v2beta2/DeploymentsListResponse": list_deployments_response
"/deploymentmanager:v2beta2/ManifestsListResponse": list_manifests_response
"/deploymentmanager:v2beta2/OperationsListResponse": list_operations_response
"/deploymentmanager:v2beta2/ResourcesListResponse": list_resources_response
"/deploymentmanager:v2beta2/TypesListResponse": list_types_response
"/dfareporting:v2.1/AccountPermissionGroupsListResponse": list_account_permission_groups_response
"/dfareporting:v2.1/AccountPermissionsListResponse": list_account_permissions_response
"/dfareporting:v2.1/AccountUserProfilesListResponse": list_account_user_profiles_response
"/dfareporting:v2.1/AccountsListResponse": list_accounts_response
"/dfareporting:v2.1/AdsListResponse": list_ads_response
"/dfareporting:v2.1/AdvertiserGroupsListResponse": list_advertiser_groups_response
"/dfareporting:v2.1/AdvertisersListResponse": list_advertisers_response
"/dfareporting:v2.1/BrowsersListResponse": list_browsers_response
"/dfareporting:v2.1/CampaignCreativeAssociationsListResponse": list_campaign_creative_associations_response
"/dfareporting:v2.1/CampaignsListResponse": list_campaigns_response
"/dfareporting:v2.1/ChangeLog/objectId": obj_id
"/dfareporting:v2.1/ChangeLogsListResponse": list_change_logs_response
"/dfareporting:v2.1/CitiesListResponse": list_cities_response
"/dfareporting:v2.1/ConnectionTypesListResponse": list_connection_types_response
"/dfareporting:v2.1/ContentCategoriesListResponse": list_content_categories_response
"/dfareporting:v2.1/CountriesListResponse": list_countries_response
"/dfareporting:v2.1/CreativeFieldValuesListResponse": list_creative_field_values_response
"/dfareporting:v2.1/CreativeFieldsListResponse": list_creative_fields_response
"/dfareporting:v2.1/CreativeGroupsListResponse": list_creative_groups_response
"/dfareporting:v2.1/CreativesListResponse": list_creatives_response
"/dfareporting:v2.1/DimensionValueRequest": dimension_value_request
"/dfareporting:v2.1/DirectorySiteContactsListResponse": list_directory_site_contacts_response
"/dfareporting:v2.1/DirectorySitesListResponse": list_directory_sites_response
"/dfareporting:v2.1/EventTagsListResponse": list_event_tags_response
"/dfareporting:v2.1/FloodlightActivitiesGenerateTagResponse": floodlight_activities_generate_tag_response
"/dfareporting:v2.1/FloodlightActivitiesListResponse": list_floodlight_activities_response
"/dfareporting:v2.1/FloodlightActivityGroupsListResponse": list_floodlight_activity_groups_response
"/dfareporting:v2.1/FloodlightConfigurationsListResponse": list_floodlight_configurations_response
"/dfareporting:v2.1/InventoryItemsListResponse": list_inventory_items_response
"/dfareporting:v2.1/LandingPagesListResponse": list_landing_pages_response
"/dfareporting:v2.1/MetrosListResponse": list_metros_response
"/dfareporting:v2.1/MobileCarriersListResponse": list_mobile_carriers_response
"/dfareporting:v2.1/ObjectFilter/objectIds/object_id": obj_id
"/dfareporting:v2.1/OperatingSystemVersionsListResponse": list_operating_system_versions_response
"/dfareporting:v2.1/OperatingSystemsListResponse": list_operating_systems_response
"/dfareporting:v2.1/OrderDocumentsListResponse": list_order_documents_response
"/dfareporting:v2.1/OrdersListResponse": list_orders_response
"/dfareporting:v2.1/PlacementGroupsListResponse": list_placement_groups_response
"/dfareporting:v2.1/PlacementStrategiesListResponse": list_placement_strategies_response
"/dfareporting:v2.1/PlacementsGenerateTagsResponse": generate_placements_tags_response
"/dfareporting:v2.1/PlacementsListResponse": list_placements_response
"/dfareporting:v2.1/PlatformTypesListResponse": list_platform_types_response
"/dfareporting:v2.1/PostalCodesListResponse": list_postal_codes_response
"/dfareporting:v2.1/ProjectsListResponse": list_projects_response
"/dfareporting:v2.1/RegionsListResponse": list_regions_response
"/dfareporting:v2.1/RemarketingListsListResponse": list_remarketing_lists_response
"/dfareporting:v2.1/SitesListResponse": list_sites_response
"/dfareporting:v2.1/SizesListResponse": list_sizes_response
"/dfareporting:v2.1/SubaccountsListResponse": list_subaccounts_response
"/dfareporting:v2.1/TargetableRemarketingListsListResponse": list_targetable_remarketing_lists_response
"/dfareporting:v2.1/UserRolePermissionGroupsListResponse": list_user_role_permission_groups_response
"/dfareporting:v2.1/UserRolePermissionsListResponse": list_user_role_permissions_response
"/dfareporting:v2.1/UserRolesListResponse": list_user_roles_response
"/dfareporting:v2.1/dfareporting.floodlightActivities.generatetag": generate_floodlight_activity_tag
"/dfareporting:v2.1/dfareporting.placements.generatetags": generate_placement_tags
"/discovery:v1/RestDescription/methods": api_methods
"/discovery:v1/RestResource/methods": api_methods
"/dns:v1/ChangesListResponse": list_changes_response
"/dns:v1/ManagedZonesListResponse": list_managed_zones_response
"/dns:v1/ResourceRecordSetsListResponse": list_resource_record_sets_response
"/doubleclickbidmanager:v1/DownloadLineItemsRequest": download_line_items_request
"/doubleclickbidmanager:v1/DownloadLineItemsResponse": download_line_items_response
"/doubleclickbidmanager:v1/ListQueriesResponse": list_queries_response
"/doubleclickbidmanager:v1/ListReportsResponse": list_reports_response
"/doubleclickbidmanager:v1/RunQueryRequest": run_query_request
"/doubleclickbidmanager:v1/UploadLineItemsRequest": upload_line_items_request
"/doubleclickbidmanager:v1/UploadLineItemsResponse": upload_line_items_response
"/doubleclickbidmanager:v1/doubleclickbidmanager.lineitems.downloadlineitems": download_line_items
"/doubleclickbidmanager:v1/doubleclickbidmanager.lineitems.uploadlineitems": upload_line_items
"/doubleclickbidmanager:v1/doubleclickbidmanager.queries.createquery": create_query
"/doubleclickbidmanager:v1/doubleclickbidmanager.queries.deletequery": deletequery
"/doubleclickbidmanager:v1/doubleclickbidmanager.queries.getquery": get_query
"/doubleclickbidmanager:v1/doubleclickbidmanager.queries.listqueries": list_queries
"/doubleclickbidmanager:v1/doubleclickbidmanager.queries.runquery": run_query
"/doubleclickbidmanager:v1/doubleclickbidmanager.reports.listreports": list_reports
"/doubleclicksearch:v2/ReportRequest": report_request
"/doubleclicksearch:v2/UpdateAvailabilityRequest": update_availability_request
"/doubleclicksearch:v2/UpdateAvailabilityResponse": update_availability_response
"/drive:v2/drive.files.emptyTrash": empty_trash
"/drive:v2/drive.permissions.getIdForEmail": get_permission_id_for_email
"/fusiontables:v2/fusiontables.table.importRows": import_rows
"/fusiontables:v2/fusiontables.table.importTable": import_table
"/games:v1/AchievementDefinitionsListResponse": list_achievement_definitions_response
"/games:v1/AchievementIncrementResponse": achievement_increment_response
"/games:v1/AchievementRevealResponse": achievement_reveal_response
"/games:v1/AchievementSetStepsAtLeastResponse": achievement_set_steps_at_least_response
"/games:v1/AchievementUnlockResponse": achievement_unlock_response
"/games:v1/AchievementUpdateMultipleRequest": achievement_update_multiple_request
"/games:v1/AchievementUpdateMultipleResponse": achievement_update_multiple_response
"/games:v1/AchievementUpdateRequest": update_achievement_request
"/games:v1/AchievementUpdateResponse": update_achievement_response
"/games:v1/CategoryListResponse": list_category_response
"/games:v1/EventDefinitionListResponse": list_event_definition_response
"/games:v1/EventRecordRequest": event_record_request
"/games:v1/EventUpdateRequest": update_event_request
"/games:v1/EventUpdateResponse": update_event_response
"/games:v1/LeaderboardListResponse": list_leaderboard_response
"/games:v1/PlayerAchievementListResponse": list_player_achievement_response
"/games:v1/PlayerEventListResponse": list_player_event_response
"/games:v1/PlayerLeaderboardScoreListResponse": list_player_leaderboard_score_response
"/games:v1/PlayerListResponse": list_player_response
"/games:v1/PlayerScoreListResponse": list_player_score_response
"/games:v1/PlayerScoreResponse": player_score_response
"/games:v1/QuestListResponse": list_quest_response
"/games:v1/RevisionCheckResponse": check_revision_response
"/games:v1/RoomCreateRequest": create_room_request
"/games:v1/RoomJoinRequest": join_room_request
"/games:v1/RoomLeaveRequest": leave_room_request
"/games:v1/SnapshotListResponse": list_snapshot_response
"/games:v1/TurnBasedMatchCreateRequest": create_turn_based_match_request
"/games:v1/TurnBasedMatchDataRequest": turn_based_match_data_request
"/games:v1/games.achievements.updateMultiple": update_multiple_achievements
"/games:v1/games.events.listDefinitions": list_event_definitions
"/games:v1/games.metagame.getMetagameConfig": get_metagame_config
"/games:v1/games.rooms.reportStatus": report_room_status
"/games:v1/games.turnBasedMatches.leaveTurn": leave_turn
"/games:v1/games.turnBasedMatches.takeTurn": take_turn
"/gamesConfiguration:v1configuration/AchievementConfigurationListResponse": list_achievement_configuration_response
"/gamesConfiguration:v1configuration/LeaderboardConfigurationListResponse": list_leaderboard_configuration_response
"/genomics:v1beta2/genomics.callsets.create": create_call_set
"/genomics:v1beta2/genomics.callsets.delete": delete_call_set
"/genomics:v1beta2/genomics.callsets.get": get_call_set
"/genomics:v1beta2/genomics.callsets.patch": patch_call_set
"/genomics:v1beta2/genomics.callsets.search": search_call_sets
"/genomics:v1beta2/genomics.callsets.update": update_call_set
"/genomics:v1beta2/genomics.readgroupsets.align": align_read_group_sets
"/genomics:v1beta2/genomics.readgroupsets.call": call_read_group_sets
"/genomics:v1beta2/genomics.readgroupsets.coveragebuckets.list": list_coverage_buckets
"/genomics:v1beta2/genomics.readgroupsets.delete": delete_read_group_set
"/genomics:v1beta2/genomics.readgroupsets.export": export_read_group_sets
"/genomics:v1beta2/genomics.readgroupsets.get": get_read_group_set
"/genomics:v1beta2/genomics.readgroupsets.import": import_read_group_sets
"/genomics:v1beta2/genomics.readgroupsets.patch": patch_read_group_set
"/genomics:v1beta2/genomics.readgroupsets.search": search_read_group_sets
"/genomics:v1beta2/genomics.readgroupsets.update": update_read_group_set
"/genomics:v1beta2/genomics.references.bases.list/end": end_position
"/genomics:v1beta2/genomics.references.bases.list/start": start_position
"/genomics:v1beta2/genomics.referencesets.get": get_reference_set
"/genomics:v1beta2/genomics.streamingReadstore.streamreads": stream_reads
"/gmail:v1/gmail.users.getProfile": get_user_profile
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyCreateAuthUriRequest": create_auth_uri_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyDeleteAccountRequest": delete_account_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyDownloadAccountRequest": download_account_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyGetAccountInfoRequest": get_account_info_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyGetPublicKeysResponse": get_public_keys_response
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyGetPublicKeysResponse/get_public_keys_response": get_public_keys_response
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyResetPasswordRequest": reset_password_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartySetAccountInfoRequest": set_account_info_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyUploadAccountRequest": upload_account_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyVerifyAssertionRequest": verify_assertion_request
"/identitytoolkit:v3/IdentitytoolkitRelyingpartyVerifyPasswordRequest": verify_password_request
"/identitytoolkit:v3/identitytoolkit.relyingparty.createAuthUri": create_auth_uri
"/identitytoolkit:v3/identitytoolkit.relyingparty.deleteAccount": delete_account
"/identitytoolkit:v3/identitytoolkit.relyingparty.downloadAccount": download_account
"/identitytoolkit:v3/identitytoolkit.relyingparty.getAccountInfo": get_account_info
"/identitytoolkit:v3/identitytoolkit.relyingparty.getOobConfirmationCode": get_oob_confirmation_code
"/identitytoolkit:v3/identitytoolkit.relyingparty.getPublicKeys": get_public_keys
"/identitytoolkit:v3/identitytoolkit.relyingparty.getRecaptchaParam": get_recaptcha_param
"/identitytoolkit:v3/identitytoolkit.relyingparty.resetPassword": reset_password
"/identitytoolkit:v3/identitytoolkit.relyingparty.setAccountInfo": set_account_info
"/identitytoolkit:v3/identitytoolkit.relyingparty.uploadAccount": upload_account
"/identitytoolkit:v3/identitytoolkit.relyingparty.verifyAssertion": verify_assertion
"/identitytoolkit:v3/identitytoolkit.relyingparty.verifyPassword": verify_password
"/licensing:v1/licensing.licenseAssignments.listForProduct": list_license_assignment_for_product
"/licensing:v1/licensing.licenseAssignments.listForProductAndSku": list_license_assignment_for_product_and_sku
"/logging:v1beta3/logging.projects.logServices.indexes.list": list_log_service_indexes
"/logging:v1beta3/logging.projects.logServices.list": list_log_services
"/logging:v1beta3/logging.projects.logServices.sinks.create": create_log_service_sink
"/logging:v1beta3/logging.projects.logServices.sinks.delete": delete_log_service_sink
"/logging:v1beta3/logging.projects.logServices.sinks.get": get_log_service_sink
"/logging:v1beta3/logging.projects.logServices.sinks.list": list_log_service_sinks
"/logging:v1beta3/logging.projects.logServices.sinks.update": update_log_service_sink
"/logging:v1beta3/logging.projects.logs.delete": delete_log
"/logging:v1beta3/logging.projects.logs.list": list_logs
"/logging:v1beta3/logging.projects.logs.sinks.create": create_log_sink
"/logging:v1beta3/logging.projects.logs.sinks.delete": delete_log_sink
"/logging:v1beta3/logging.projects.logs.sinks.get": get_log_sink
"/logging:v1beta3/logging.projects.logs.sinks.list": list_log_sinks
"/logging:v1beta3/logging.projects.logs.sinks.update": update_log_sink
"/manager:v1beta2/DeploymentsListResponse": list_deployments_response
"/manager:v1beta2/TemplatesListResponse": list_templates_response
"/mapsengine:v1/AssetsListResponse": list_assets_response
"/mapsengine:v1/FeaturesBatchDeleteRequest": batch_delete_features_request
"/mapsengine:v1/FeaturesBatchInsertRequest": batch_insert_features_request
"/mapsengine:v1/FeaturesBatchPatchRequest": batch_patch_features_request
"/mapsengine:v1/FeaturesListResponse": list_features_response
"/mapsengine:v1/IconsListResponse": list_icons_response
"/mapsengine:v1/LayersListResponse": list_layers_response
"/mapsengine:v1/MapsListResponse": list_maps_response
"/mapsengine:v1/ParentsListResponse": list_parents_response
"/mapsengine:v1/PermissionsBatchDeleteRequest": batch_delete_permissions_request
"/mapsengine:v1/PermissionsBatchDeleteResponse": batch_delete_permissions_response
"/mapsengine:v1/PermissionsBatchUpdateRequest": batch_update_permissions_request
"/mapsengine:v1/PermissionsBatchUpdateResponse": batch_update_permissions_response
"/mapsengine:v1/PermissionsListResponse": list_permissions_response
"/mapsengine:v1/ProjectsListResponse": list_projects_response
"/mapsengine:v1/PublishedLayersListResponse": list_published_layers_response
"/mapsengine:v1/PublishedMapsListResponse": list_published_maps_response
"/mapsengine:v1/RasterCollectionsListResponse": list_raster_collections_response
"/mapsengine:v1/RasterCollectionsRasterBatchDeleteRequest": batch_delete_raster_collections_raster_request
"/mapsengine:v1/RasterCollectionsRastersBatchDeleteResponse": batch_delete_raster_collections_rasters_response
"/mapsengine:v1/RasterCollectionsRastersBatchInsertRequest": batch_insert_raster_collections_rasters_request
"/mapsengine:v1/RasterCollectionsRastersBatchInsertResponse": batch_insert_raster_collections_rasters_response
"/mapsengine:v1/RasterCollectionsRastersListResponse": list_raster_collections_rasters_response
"/mapsengine:v1/RastersListResponse": list_rasters_response
"/mapsengine:v1/TablesListResponse": list_tables_response
"/mirror:v1/AttachmentsListResponse": list_attachments_response
"/mirror:v1/ContactsListResponse": list_contacts_response
"/mirror:v1/LocationsListResponse": list_locations_response
"/mirror:v1/SubscriptionsListResponse": list_subscriptions_response
"/mirror:v1/TimelineListResponse": list_timeline_response
"/oauth2:v2/oauth2.userinfo.v2.me.get": get_userinfo_v2
"/pagespeedonline:v2/PagespeedApiFormatStringV2": format_string
"/pagespeedonline:v2/PagespeedApiImageV2": image
"/pagespeedonline:v2/pagespeedonline.pagespeedapi.runpagespeed": run_pagespeed
"/plus:v1/plus.people.listByActivity": list_people_by_activity
"/plusDomains:v1/plusDomains.circles.addPeople": add_people
"/plusDomains:v1/plusDomains.circles.removePeople": remove_people
"/plusDomains:v1/plusDomains.people.listByActivity": list_people_by_activity
"/plusDomains:v1/plusDomains.people.listByCircle": list_people_by_circle
"/prediction:v1.6/prediction.hostedmodels.predict": predict_hosted_model
"/prediction:v1.6/prediction.trainedmodels.analyze": analyze_trained_model
"/prediction:v1.6/prediction.trainedmodels.delete": delete_trained_model
"/prediction:v1.6/prediction.trainedmodels.get": get_trained_model
"/prediction:v1.6/prediction.trainedmodels.insert": insert_trained_model
"/prediction:v1.6/prediction.trainedmodels.list": list_trained_models
"/prediction:v1.6/prediction.trainedmodels.predict": predict_trained_model
"/prediction:v1.6/prediction.trainedmodels.update": update_trained_model
"/pubsub:v1beta2/PubsubMessage": message
"/pubsub:v1beta2/pubsub.projects.subscriptions.create": create_subscription
"/pubsub:v1beta2/pubsub.projects.subscriptions.delete": delete_subscription
"/pubsub:v1beta2/pubsub.projects.subscriptions.get": get_subscription
"/pubsub:v1beta2/pubsub.projects.subscriptions.list": list_subscriptions
"/pubsub:v1beta2/pubsub.projects.subscriptions.setIamPolicy": set_subscription_policy
"/pubsub:v1beta2/pubsub.projects.subscriptions.testIamPermissions": test_subscription_permissions
"/pubsub:v1beta2/pubsub.projects.topics.create": create_topic
"/pubsub:v1beta2/pubsub.projects.topics.delete": delete_topic
"/pubsub:v1beta2/pubsub.projects.topics.get": get_topic
"/pubsub:v1beta2/pubsub.projects.topics.list": list_topics
"/pubsub:v1beta2/pubsub.projects.topics.setIamPolicy": set_topic_policy
"/pubsub:v1beta2/pubsub.projects.topics.testIamPermissions": test_topic_permissions
"/pubsub:v1beta2/pubsub.projects.topics.subscriptions.list": list_topic_subscriptions
"/qpxExpress:v1/TripsSearchRequest": search_trips_request
"/qpxExpress:v1/TripsSearchResponse": search_trips_response
"/replicapool:v1beta2/InstanceGroupManagersAbandonInstancesRequest": abandon_instances_request
"/replicapool:v1beta2/InstanceGroupManagersDeleteInstancesRequest": delete_instances_request
"/replicapool:v1beta2/InstanceGroupManagersRecreateInstancesRequest": recreate_instances_request
"/replicapool:v1beta2/InstanceGroupManagersSetInstanceTemplateRequest": set_instance_template_request
"/replicapool:v1beta2/InstanceGroupManagersSetTargetPoolsRequest": set_target_pools_request
"/replicapool:v1beta2/replicapool.instanceGroupManagers.abandonInstances": abandon_instances
"/replicapool:v1beta2/replicapool.instanceGroupManagers.deleteInstances": delete_instances
"/replicapool:v1beta2/replicapool.instanceGroupManagers.recreateInstances": recreate_instances
"/replicapool:v1beta2/replicapool.instanceGroupManagers.resize": resize_instance
"/replicapool:v1beta2/replicapool.instanceGroupManagers.setInstanceTemplate": set_instance_template
"/replicapool:v1beta2/replicapool.instanceGroupManagers.setTargetPools": set_target_pools
"/replicapoolupdater:v1beta1/replicapoolupdater.rollingUpdates.listInstanceUpdates": list_instance_updates
"/reseller:v1/ChangePlanRequest": change_plan_request
"/reseller:v1/reseller.subscriptions.changeRenewalSettings": change_subscription_renewal_settings
"/reseller:v1/reseller.subscriptions.changeSeats": change_subscription_seats
"/resourceviews:v1beta2/ZoneViewsAddResourcesRequest": add_resources_request
"/resourceviews:v1beta2/ZoneViewsGetServiceResponse": get_service_response
"/resourceviews:v1beta2/ZoneViewsListResourcesResponse": list_resources_response
"/resourceviews:v1beta2/ZoneViewsRemoveResourcesRequest": remove_resources_request
"/resourceviews:v1beta2/ZoneViewsSetServiceRequest": set_service_request
"/siteVerification:v1/SiteVerificationWebResourceGettokenRequest": get_web_resource_token_request
"/siteVerification:v1/SiteVerificationWebResourceGettokenResponse": get_web_resource_token_response
"/siteVerification:v1/SiteVerificationWebResourceGettokenResponse/method": verification_method
"/siteVerification:v1/SiteVerificationWebResourceListResponse": list_web_resource_response
"/sqladmin:v1beta4/BackupRunsListResponse": list_backup_runs_response
"/sqladmin:v1beta4/DatabasesListResponse": list_databases_response
"/sqladmin:v1beta4/FlagsListResponse": list_flags_response
"/sqladmin:v1beta4/InstancesCloneRequest": clone_instances_request
"/sqladmin:v1beta4/InstancesExportRequest": export_instances_request
"/sqladmin:v1beta4/InstancesImportRequest": import_instances_request
"/sqladmin:v1beta4/InstancesListResponse": list_instances_response
"/sqladmin:v1beta4/InstancesRestoreBackupRequest": restore_instances_backup_request
"/sqladmin:v1beta4/OperationsListResponse": list_operations_response
"/sqladmin:v1beta4/SslCertsInsertRequest": insert_ssl_certs_request
"/sqladmin:v1beta4/SslCertsInsertResponse": insert_ssl_certs_response
"/sqladmin:v1beta4/SslCertsListResponse": list_ssl_certs_response
"/sqladmin:v1beta4/TiersListResponse": list_tiers_response
"/sqladmin:v1beta4/UsersListResponse": list_users_response
"/storage:v1/Bucket/cors": cors_configurations
"/storage:v1/Bucket/cors/cors_configuration/method": http_method
"/tagmanager:v1/tagmanager.accounts.containers.create": create_container
"/tagmanager:v1/tagmanager.accounts.containers.delete": delete_container
"/tagmanager:v1/tagmanager.accounts.containers.get": get_container
"/tagmanager:v1/tagmanager.accounts.containers.list": list_containers
"/tagmanager:v1/tagmanager.accounts.containers.macros.create": create_macro
"/tagmanager:v1/tagmanager.accounts.containers.macros.delete": delete_macro
"/tagmanager:v1/tagmanager.accounts.containers.macros.get": get_macro
"/tagmanager:v1/tagmanager.accounts.containers.macros.list": list_macros
"/tagmanager:v1/tagmanager.accounts.containers.macros.update": update_macro
"/tagmanager:v1/tagmanager.accounts.containers.rules.create": create_rule
"/tagmanager:v1/tagmanager.accounts.containers.rules.delete": delete_rule
"/tagmanager:v1/tagmanager.accounts.containers.rules.get": get_rule
"/tagmanager:v1/tagmanager.accounts.containers.rules.list": list_rules
"/tagmanager:v1/tagmanager.accounts.containers.rules.update": update_rule
"/tagmanager:v1/tagmanager.accounts.containers.tags.create": create_tag
"/tagmanager:v1/tagmanager.accounts.containers.tags.delete": delete_tag
"/tagmanager:v1/tagmanager.accounts.containers.tags.get": get_tag
"/tagmanager:v1/tagmanager.accounts.containers.tags.list": list_tags
"/tagmanager:v1/tagmanager.accounts.containers.tags.update": update_tag
"/tagmanager:v1/tagmanager.accounts.containers.triggers.create": create_trigger
"/tagmanager:v1/tagmanager.accounts.containers.triggers.delete": delete_trigger
"/tagmanager:v1/tagmanager.accounts.containers.triggers.get": get_trigger
"/tagmanager:v1/tagmanager.accounts.containers.triggers.list": list_triggers
"/tagmanager:v1/tagmanager.accounts.containers.triggers.update": update_trigger
"/tagmanager:v1/tagmanager.accounts.containers.update": update_container
"/tagmanager:v1/tagmanager.accounts.containers.variables.create": create_variable
"/tagmanager:v1/tagmanager.accounts.containers.variables.delete": delete_variable
"/tagmanager:v1/tagmanager.accounts.containers.variables.get": get_variable
"/tagmanager:v1/tagmanager.accounts.containers.variables.list": list_variables
"/tagmanager:v1/tagmanager.accounts.containers.variables.update": update_variable
"/tagmanager:v1/tagmanager.accounts.containers.versions.create": create_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.delete": delete_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.get": get_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.list": list_versions
"/tagmanager:v1/tagmanager.accounts.containers.versions.publish": publish_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.restore": restore_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.undelete": undelete_version
"/tagmanager:v1/tagmanager.accounts.containers.versions.update": update_version
"/tagmanager:v1/tagmanager.accounts.get": get_account
"/tagmanager:v1/tagmanager.accounts.list": list_accounts
"/tagmanager:v1/tagmanager.accounts.permissions.create": create_permission
"/tagmanager:v1/tagmanager.accounts.permissions.delete": delete_permission
"/tagmanager:v1/tagmanager.accounts.permissions.get": get_permission
"/tagmanager:v1/tagmanager.accounts.permissions.list": list_permissions
"/tagmanager:v1/tagmanager.accounts.permissions.update": update_permission
"/tagmanager:v1/tagmanager.accounts.update": update_account
"/translate:v2/DetectionsListResponse": list_detections_response
"/translate:v2/LanguagesListResponse": list_languages_response
"/translate:v2/TranslationsListResponse": list_translations_response
"/webmasters:v3/SitemapsListResponse": list_sitemaps_response
"/webmasters:v3/SitesListResponse": list_sites_response
"/webmasters:v3/UrlCrawlErrorsCountsQueryResponse": query_url_crawl_errors_counts_response
"/webmasters:v3/UrlCrawlErrorsSamplesListResponse": list_url_crawl_errors_samples_response
"/webmasters:v3/webmasters.urlcrawlerrorscounts.query": query_errors_count
"/webmasters:v3/webmasters.urlcrawlerrorssamples.get": get_errors_sample
"/webmasters:v3/webmasters.urlcrawlerrorssamples.list": list_errors_samples
"/webmasters:v3/webmasters.urlcrawlerrorssamples.markAsFixed": mark_as_fixed
"/youtube:v3/youtube.comments.setModerationStatus": set_comment_moderation_status
"/youtube:v3/ActivityListResponse": list_activities_response
"/youtube:v3/CaptionListResponse": list_captions_response
"/youtube:v3/ChannelListResponse": list_channels_response
"/youtube:v3/ChannelSectionListResponse": list_channel_sections_response
"/youtube:v3/CommentListResponse": list_comments_response
"/youtube:v3/CommentThreadListResponse": list_comment_threads_response
"/youtube:v3/GuideCategoryListResponse": list_guide_categories_response
"/youtube:v3/I18nLanguageListResponse": list_i18n_languages_response
"/youtube:v3/I18nRegionListResponse": list_i18n_regions_response
"/youtube:v3/LiveBroadcastListResponse": list_live_broadcasts_response
"/youtube:v3/LiveStreamListResponse": list_live_streams_response
"/youtube:v3/PlaylistItemListResponse": list_playlist_items_response
"/youtube:v3/PlaylistListResponse": list_playlist_response
"/youtube:v3/SearchListResponse": search_lists_response
"/youtube:v3/SubscriptionListResponse": list_subscription_response
"/youtube:v3/ThumbnailSetResponse": set_thumbnail_response
"/youtube:v3/VideoAbuseReportReasonListResponse": list_video_abuse_report_reason_response
"/youtube:v3/VideoCategoryListResponse": list_video_category_response
"/youtube:v3/VideoGetRatingResponse": get_video_rating_response
"/youtube:v3/VideoListResponse": list_videos_response
"/youtubeAnalytics:v1/GroupItemListResponse": list_group_item_response
"/youtubeAnalytics:v1/GroupListResponse": list_groups_response

20797
api_names_out.yaml Normal file

File diff suppressed because it is too large Load Diff

93
bin/generate-api Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env ruby
# TODO - Repeated params
require 'thor'
require 'open-uri'
require 'google/apis/discovery_v1'
require 'google/apis/generator'
require 'multi_json'
require 'logger'
module Google
class ApiGenerator < Thor
include Thor::Actions
Google::Apis::ClientOptions.default.application_name = "generate-api"
Google::Apis::ClientOptions.default.application_version = Google::Apis::VERSION
Discovery = Google::Apis::DiscoveryV1
desc 'gen OUTDIR', 'Generate ruby API from an API description'
method_options url: :string, file: :string, id: :array, preferred_only: :boolean, verbose: :boolean, names: :string, names_out: :string
method_option :preferred_only, default: true
def gen(dir)
self.destination_root = dir
Google::Apis.logger.level = Logger::DEBUG if options[:verbose]
if options[:url]
generate_from_url(options[:url])
elsif options[:file]
generate_from_file(options[:file])
else
generate_from_discovery(preferred_only: options[:preferred_only], id: options[:id] )
end
create_file(options[:names_out]) { |*| generator.dump_api_names } if options[:names_out]
end
desc 'list', 'List public APIs'
method_options verbose: :boolean, preferred_only: :boolean
def list
Google::Apis.logger.level = Logger::DEBUG if options[:verbose]
discovery = Discovery::DiscoveryService.new
apis = discovery.list_apis
apis.items.each do |api|
say sprintf('%s - %s', api.id, api.description).strip unless options[:preferred_only] && !api.preferred?
end
end
no_commands do
def generate_from_url(url)
json = discovery.http(:get, url)
generate_api(json)
end
def generate_from_file(file)
File.open(file) do |f|
generate_api(f.read)
end
end
def generate_from_discovery(preferred_only: false, id: nil)
say 'Fetching API list'
id = Array(id)
apis = discovery.list_apis
apis.items.each do |api|
if (id.empty? && preferred_only && api.preferred?) || id.include?(api.id)
say sprintf('Loading %s, version %s from %s', api.name, api.version, api.discovery_rest_url)
generate_from_url(api.discovery_rest_url)
else
say sprintf('Ignoring disoverable API %s', api.id)
end
end
end
def generate_api(json)
files = generator.render(json)
files.each do |file, content|
create_file(file) { |*| content }
end
end
def discovery
@discovery ||= Discovery::DiscoveryService.new
end
def generator
@generator ||= Google::Apis::Generator.new(api_names: options[:names])
end
end
end
end
Google::ApiGenerator.start(ARGV)

View File

@ -1,4 +1,4 @@
# Copyright 2013 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,17 +12,21 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'faraday' require 'google/apis/discovery_v1/service.rb'
require 'signet/oauth_2/client' require 'google/apis/discovery_v1/classes.rb'
require 'google/apis/discovery_v1/representations.rb'
module Google module Google
class APIClient module Apis
class ComputeServiceAccount < Signet::OAuth2::Client # APIs Discovery Service
def fetch_access_token(options={}) #
connection = options[:connection] || Faraday.default_connection # Lets you discover information about other Google APIs, such as what APIs are
response = connection.get 'http://metadata/computeMetadata/v1beta1/instance/service-accounts/default/token' # available, the resource and method details for each API.
Signet::OAuth2.parse_credentials(response.body, response.headers['content-type']) #
end # @see https://developers.google.com/discovery/
module DiscoveryV1
VERSION = 'V1'
REVISION = ''
end end
end end
end end

View File

@ -0,0 +1,947 @@
# Copyright 2015 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 'date'
require 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
module Google
module Apis
module DiscoveryV1
#
class DirectoryList
include Google::Apis::Core::Hashable
# Indicate the version of the Discovery API used to generate this doc.
# Corresponds to the JSON property `discoveryVersion`
# @return [String]
attr_accessor :discovery_version
# The individual directory entries. One entry per api/version pair.
# Corresponds to the JSON property `items`
# @return [Array<Google::Apis::DiscoveryV1::DirectoryList::Item>]
attr_accessor :items
# The kind for this response.
# Corresponds to the JSON property `kind`
# @return [String]
attr_accessor :kind
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@discovery_version = args[:discovery_version] unless args[:discovery_version].nil?
@items = args[:items] unless args[:items].nil?
@kind = args[:kind] unless args[:kind].nil?
end
#
class Item
include Google::Apis::Core::Hashable
# The description of this API.
# Corresponds to the JSON property `description`
# @return [String]
attr_accessor :description
# A link to the discovery document.
# Corresponds to the JSON property `discoveryLink`
# @return [String]
attr_accessor :discovery_link
# The URL for the discovery REST document.
# Corresponds to the JSON property `discoveryRestUrl`
# @return [String]
attr_accessor :discovery_rest_url
# A link to human readable documentation for the API.
# Corresponds to the JSON property `documentationLink`
# @return [String]
attr_accessor :documentation_link
# Links to 16x16 and 32x32 icons representing the API.
# Corresponds to the JSON property `icons`
# @return [Google::Apis::DiscoveryV1::DirectoryList::Item::Icons]
attr_accessor :icons
# The id of this API.
# Corresponds to the JSON property `id`
# @return [String]
attr_accessor :id
# The kind for this response.
# Corresponds to the JSON property `kind`
# @return [String]
attr_accessor :kind
# Labels for the status of this API, such as labs or deprecated.
# Corresponds to the JSON property `labels`
# @return [Array<String>]
attr_accessor :labels
# The name of the API.
# Corresponds to the JSON property `name`
# @return [String]
attr_accessor :name
# True if this version is the preferred version to use.
# Corresponds to the JSON property `preferred`
# @return [Boolean]
attr_accessor :preferred
alias_method :preferred?, :preferred
# The title of this API.
# Corresponds to the JSON property `title`
# @return [String]
attr_accessor :title
# The version of the API.
# Corresponds to the JSON property `version`
# @return [String]
attr_accessor :version
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@description = args[:description] unless args[:description].nil?
@discovery_link = args[:discovery_link] unless args[:discovery_link].nil?
@discovery_rest_url = args[:discovery_rest_url] unless args[:discovery_rest_url].nil?
@documentation_link = args[:documentation_link] unless args[:documentation_link].nil?
@icons = args[:icons] unless args[:icons].nil?
@id = args[:id] unless args[:id].nil?
@kind = args[:kind] unless args[:kind].nil?
@labels = args[:labels] unless args[:labels].nil?
@name = args[:name] unless args[:name].nil?
@preferred = args[:preferred] unless args[:preferred].nil?
@title = args[:title] unless args[:title].nil?
@version = args[:version] unless args[:version].nil?
end
# Links to 16x16 and 32x32 icons representing the API.
class Icons
include Google::Apis::Core::Hashable
# The URL of the 16x16 icon.
# Corresponds to the JSON property `x16`
# @return [String]
attr_accessor :x16
# The URL of the 32x32 icon.
# Corresponds to the JSON property `x32`
# @return [String]
attr_accessor :x32
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@x16 = args[:x16] unless args[:x16].nil?
@x32 = args[:x32] unless args[:x32].nil?
end
end
end
end
#
class JsonSchema
include Google::Apis::Core::Hashable
# A reference to another schema. The value of this property is the "id" of
# another schema.
# Corresponds to the JSON property `$ref`
# @return [String]
attr_accessor :_ref
# If this is a schema for an object, this property is the schema for any
# additional properties with dynamic keys on this object.
# Corresponds to the JSON property `additionalProperties`
# @return [Google::Apis::DiscoveryV1::JsonSchema]
attr_accessor :additional_properties
# Additional information about this property.
# Corresponds to the JSON property `annotations`
# @return [Google::Apis::DiscoveryV1::JsonSchema::Annotations]
attr_accessor :annotations
# The default value of this property (if one exists).
# Corresponds to the JSON property `default`
# @return [String]
attr_accessor :default
# A description of this object.
# Corresponds to the JSON property `description`
# @return [String]
attr_accessor :description
# Values this parameter may take (if it is an enum).
# Corresponds to the JSON property `enum`
# @return [Array<String>]
attr_accessor :enum
# The descriptions for the enums. Each position maps to the corresponding value
# in the "enum" array.
# Corresponds to the JSON property `enumDescriptions`
# @return [Array<String>]
attr_accessor :enum_descriptions
# An additional regular expression or key that helps constrain the value. For
# more details see: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-
# 5.23
# Corresponds to the JSON property `format`
# @return [String]
attr_accessor :format
# Unique identifier for this schema.
# Corresponds to the JSON property `id`
# @return [String]
attr_accessor :id
# If this is a schema for an array, this property is the schema for each element
# in the array.
# Corresponds to the JSON property `items`
# @return [Google::Apis::DiscoveryV1::JsonSchema]
attr_accessor :items
# Whether this parameter goes in the query or the path for REST requests.
# Corresponds to the JSON property `location`
# @return [String]
attr_accessor :location
# The maximum value of this parameter.
# Corresponds to the JSON property `maximum`
# @return [String]
attr_accessor :maximum
# The minimum value of this parameter.
# Corresponds to the JSON property `minimum`
# @return [String]
attr_accessor :minimum
# The regular expression this parameter must conform to. Uses Java 6 regex
# format: http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html
# Corresponds to the JSON property `pattern`
# @return [String]
attr_accessor :pattern
# If this is a schema for an object, list the schema for each property of this
# object.
# Corresponds to the JSON property `properties`
# @return [Hash<String,Google::Apis::DiscoveryV1::JsonSchema>]
attr_accessor :properties
# The value is read-only, generated by the service. The value cannot be modified
# by the client. If the value is included in a POST, PUT, or PATCH request, it
# is ignored by the service.
# Corresponds to the JSON property `readOnly`
# @return [Boolean]
attr_accessor :read_only
alias_method :read_only?, :read_only
# Whether this parameter may appear multiple times.
# Corresponds to the JSON property `repeated`
# @return [Boolean]
attr_accessor :repeated
alias_method :repeated?, :repeated
# Whether the parameter is required.
# Corresponds to the JSON property `required`
# @return [Boolean]
attr_accessor :required
alias_method :required?, :required
# The value type for this schema. A list of values can be found here: http://
# tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
# Corresponds to the JSON property `type`
# @return [String]
attr_accessor :type
# In a variant data type, the value of one property is used to determine how to
# interpret the entire entity. Its value must exist in a map of descriminant
# values to schema names.
# Corresponds to the JSON property `variant`
# @return [Google::Apis::DiscoveryV1::JsonSchema::Variant]
attr_accessor :variant
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@_ref = args[:_ref] unless args[:_ref].nil?
@additional_properties = args[:additional_properties] unless args[:additional_properties].nil?
@annotations = args[:annotations] unless args[:annotations].nil?
@default = args[:default] unless args[:default].nil?
@description = args[:description] unless args[:description].nil?
@enum = args[:enum] unless args[:enum].nil?
@enum_descriptions = args[:enum_descriptions] unless args[:enum_descriptions].nil?
@format = args[:format] unless args[:format].nil?
@id = args[:id] unless args[:id].nil?
@items = args[:items] unless args[:items].nil?
@location = args[:location] unless args[:location].nil?
@maximum = args[:maximum] unless args[:maximum].nil?
@minimum = args[:minimum] unless args[:minimum].nil?
@pattern = args[:pattern] unless args[:pattern].nil?
@properties = args[:properties] unless args[:properties].nil?
@read_only = args[:read_only] unless args[:read_only].nil?
@repeated = args[:repeated] unless args[:repeated].nil?
@required = args[:required] unless args[:required].nil?
@type = args[:type] unless args[:type].nil?
@variant = args[:variant] unless args[:variant].nil?
end
# Additional information about this property.
class Annotations
include Google::Apis::Core::Hashable
# A list of methods for which this property is required on requests.
# Corresponds to the JSON property `required`
# @return [Array<String>]
attr_accessor :required
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@required = args[:required] unless args[:required].nil?
end
end
# In a variant data type, the value of one property is used to determine how to
# interpret the entire entity. Its value must exist in a map of descriminant
# values to schema names.
class Variant
include Google::Apis::Core::Hashable
# The name of the type discriminant property.
# Corresponds to the JSON property `discriminant`
# @return [String]
attr_accessor :discriminant
# The map of discriminant value to schema to use for parsing..
# Corresponds to the JSON property `map`
# @return [Array<Google::Apis::DiscoveryV1::JsonSchema::Variant::Map>]
attr_accessor :map
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@discriminant = args[:discriminant] unless args[:discriminant].nil?
@map = args[:map] unless args[:map].nil?
end
#
class Map
include Google::Apis::Core::Hashable
#
# Corresponds to the JSON property `$ref`
# @return [String]
attr_accessor :_ref
#
# Corresponds to the JSON property `type_value`
# @return [String]
attr_accessor :type_value
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@_ref = args[:_ref] unless args[:_ref].nil?
@type_value = args[:type_value] unless args[:type_value].nil?
end
end
end
end
#
class RestDescription
include Google::Apis::Core::Hashable
# Authentication information.
# Corresponds to the JSON property `auth`
# @return [Google::Apis::DiscoveryV1::RestDescription::Auth]
attr_accessor :auth
# [DEPRECATED] The base path for REST requests.
# Corresponds to the JSON property `basePath`
# @return [String]
attr_accessor :base_path
# [DEPRECATED] The base URL for REST requests.
# Corresponds to the JSON property `baseUrl`
# @return [String]
attr_accessor :base_url
# The path for REST batch requests.
# Corresponds to the JSON property `batchPath`
# @return [String]
attr_accessor :batch_path
# Indicates how the API name should be capitalized and split into various parts.
# Useful for generating pretty class names.
# Corresponds to the JSON property `canonicalName`
# @return [String]
attr_accessor :canonical_name
# The description of this API.
# Corresponds to the JSON property `description`
# @return [String]
attr_accessor :description
# Indicate the version of the Discovery API used to generate this doc.
# Corresponds to the JSON property `discoveryVersion`
# @return [String]
attr_accessor :discovery_version
# A link to human readable documentation for the API.
# Corresponds to the JSON property `documentationLink`
# @return [String]
attr_accessor :documentation_link
# The ETag for this response.
# Corresponds to the JSON property `etag`
# @return [String]
attr_accessor :etag
# A list of supported features for this API.
# Corresponds to the JSON property `features`
# @return [Array<String>]
attr_accessor :features
# Links to 16x16 and 32x32 icons representing the API.
# Corresponds to the JSON property `icons`
# @return [Google::Apis::DiscoveryV1::RestDescription::Icons]
attr_accessor :icons
# The ID of this API.
# Corresponds to the JSON property `id`
# @return [String]
attr_accessor :id
# The kind for this response.
# Corresponds to the JSON property `kind`
# @return [String]
attr_accessor :kind
# Labels for the status of this API, such as labs or deprecated.
# Corresponds to the JSON property `labels`
# @return [Array<String>]
attr_accessor :labels
# API-level methods for this API.
# Corresponds to the JSON property `methods`
# @return [Hash<String,Google::Apis::DiscoveryV1::RestMethod>]
attr_accessor :api_methods
# The name of this API.
# Corresponds to the JSON property `name`
# @return [String]
attr_accessor :name
# The domain of the owner of this API. Together with the ownerName and a
# packagePath values, this can be used to generate a library for this API which
# would have a unique fully qualified name.
# Corresponds to the JSON property `ownerDomain`
# @return [String]
attr_accessor :owner_domain
# The name of the owner of this API. See ownerDomain.
# Corresponds to the JSON property `ownerName`
# @return [String]
attr_accessor :owner_name
# The package of the owner of this API. See ownerDomain.
# Corresponds to the JSON property `packagePath`
# @return [String]
attr_accessor :package_path
# Common parameters that apply across all apis.
# Corresponds to the JSON property `parameters`
# @return [Hash<String,Google::Apis::DiscoveryV1::JsonSchema>]
attr_accessor :parameters
# The protocol described by this document.
# Corresponds to the JSON property `protocol`
# @return [String]
attr_accessor :protocol
# The resources in this API.
# Corresponds to the JSON property `resources`
# @return [Hash<String,Google::Apis::DiscoveryV1::RestResource>]
attr_accessor :resources
# The version of this API.
# Corresponds to the JSON property `revision`
# @return [String]
attr_accessor :revision
# The root URL under which all API services live.
# Corresponds to the JSON property `rootUrl`
# @return [String]
attr_accessor :root_url
# The schemas for this API.
# Corresponds to the JSON property `schemas`
# @return [Hash<String,Google::Apis::DiscoveryV1::JsonSchema>]
attr_accessor :schemas
# The base path for all REST requests.
# Corresponds to the JSON property `servicePath`
# @return [String]
attr_accessor :service_path
# The title of this API.
# Corresponds to the JSON property `title`
# @return [String]
attr_accessor :title
# The version of this API.
# Corresponds to the JSON property `version`
# @return [String]
attr_accessor :version
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@auth = args[:auth] unless args[:auth].nil?
@base_path = args[:base_path] unless args[:base_path].nil?
@base_url = args[:base_url] unless args[:base_url].nil?
@batch_path = args[:batch_path] unless args[:batch_path].nil?
@canonical_name = args[:canonical_name] unless args[:canonical_name].nil?
@description = args[:description] unless args[:description].nil?
@discovery_version = args[:discovery_version] unless args[:discovery_version].nil?
@documentation_link = args[:documentation_link] unless args[:documentation_link].nil?
@etag = args[:etag] unless args[:etag].nil?
@features = args[:features] unless args[:features].nil?
@icons = args[:icons] unless args[:icons].nil?
@id = args[:id] unless args[:id].nil?
@kind = args[:kind] unless args[:kind].nil?
@labels = args[:labels] unless args[:labels].nil?
@api_methods = args[:api_methods] unless args[:api_methods].nil?
@name = args[:name] unless args[:name].nil?
@owner_domain = args[:owner_domain] unless args[:owner_domain].nil?
@owner_name = args[:owner_name] unless args[:owner_name].nil?
@package_path = args[:package_path] unless args[:package_path].nil?
@parameters = args[:parameters] unless args[:parameters].nil?
@protocol = args[:protocol] unless args[:protocol].nil?
@resources = args[:resources] unless args[:resources].nil?
@revision = args[:revision] unless args[:revision].nil?
@root_url = args[:root_url] unless args[:root_url].nil?
@schemas = args[:schemas] unless args[:schemas].nil?
@service_path = args[:service_path] unless args[:service_path].nil?
@title = args[:title] unless args[:title].nil?
@version = args[:version] unless args[:version].nil?
end
# Authentication information.
class Auth
include Google::Apis::Core::Hashable
# OAuth 2.0 authentication information.
# Corresponds to the JSON property `oauth2`
# @return [Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2]
attr_accessor :oauth2
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@oauth2 = args[:oauth2] unless args[:oauth2].nil?
end
# OAuth 2.0 authentication information.
class Oauth2
include Google::Apis::Core::Hashable
# Available OAuth 2.0 scopes.
# Corresponds to the JSON property `scopes`
# @return [Hash<String,Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2::Scope>]
attr_accessor :scopes
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@scopes = args[:scopes] unless args[:scopes].nil?
end
# The scope value.
class Scope
include Google::Apis::Core::Hashable
# Description of scope.
# Corresponds to the JSON property `description`
# @return [String]
attr_accessor :description
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@description = args[:description] unless args[:description].nil?
end
end
end
end
# Links to 16x16 and 32x32 icons representing the API.
class Icons
include Google::Apis::Core::Hashable
# The URL of the 16x16 icon.
# Corresponds to the JSON property `x16`
# @return [String]
attr_accessor :x16
# The URL of the 32x32 icon.
# Corresponds to the JSON property `x32`
# @return [String]
attr_accessor :x32
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@x16 = args[:x16] unless args[:x16].nil?
@x32 = args[:x32] unless args[:x32].nil?
end
end
end
#
class RestMethod
include Google::Apis::Core::Hashable
# Description of this method.
# Corresponds to the JSON property `description`
# @return [String]
attr_accessor :description
# Whether this method requires an ETag to be specified. The ETag is sent as an
# HTTP If-Match or If-None-Match header.
# Corresponds to the JSON property `etagRequired`
# @return [Boolean]
attr_accessor :etag_required
alias_method :etag_required?, :etag_required
# HTTP method used by this method.
# Corresponds to the JSON property `httpMethod`
# @return [String]
attr_accessor :http_method
# A unique ID for this method. This property can be used to match methods
# between different versions of Discovery.
# Corresponds to the JSON property `id`
# @return [String]
attr_accessor :id
# Media upload parameters.
# Corresponds to the JSON property `mediaUpload`
# @return [Google::Apis::DiscoveryV1::RestMethod::MediaUpload]
attr_accessor :media_upload
# Ordered list of required parameters, serves as a hint to clients on how to
# structure their method signatures. The array is ordered such that the "most-
# significant" parameter appears first.
# Corresponds to the JSON property `parameterOrder`
# @return [Array<String>]
attr_accessor :parameter_order
# Details for all parameters in this method.
# Corresponds to the JSON property `parameters`
# @return [Hash<String,Google::Apis::DiscoveryV1::JsonSchema>]
attr_accessor :parameters
# The URI path of this REST method. Should be used in conjunction with the
# basePath property at the api-level.
# Corresponds to the JSON property `path`
# @return [String]
attr_accessor :path
# The schema for the request.
# Corresponds to the JSON property `request`
# @return [Google::Apis::DiscoveryV1::RestMethod::Request]
attr_accessor :request
# The schema for the response.
# Corresponds to the JSON property `response`
# @return [Google::Apis::DiscoveryV1::RestMethod::Response]
attr_accessor :response
# OAuth 2.0 scopes applicable to this method.
# Corresponds to the JSON property `scopes`
# @return [Array<String>]
attr_accessor :scopes
# Whether this method supports media downloads.
# Corresponds to the JSON property `supportsMediaDownload`
# @return [Boolean]
attr_accessor :supports_media_download
alias_method :supports_media_download?, :supports_media_download
# Whether this method supports media uploads.
# Corresponds to the JSON property `supportsMediaUpload`
# @return [Boolean]
attr_accessor :supports_media_upload
alias_method :supports_media_upload?, :supports_media_upload
# Whether this method supports subscriptions.
# Corresponds to the JSON property `supportsSubscription`
# @return [Boolean]
attr_accessor :supports_subscription
alias_method :supports_subscription?, :supports_subscription
# Indicates that downloads from this method should use the download service URL (
# i.e. "/download"). Only applies if the method supports media download.
# Corresponds to the JSON property `useMediaDownloadService`
# @return [Boolean]
attr_accessor :use_media_download_service
alias_method :use_media_download_service?, :use_media_download_service
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@description = args[:description] unless args[:description].nil?
@etag_required = args[:etag_required] unless args[:etag_required].nil?
@http_method = args[:http_method] unless args[:http_method].nil?
@id = args[:id] unless args[:id].nil?
@media_upload = args[:media_upload] unless args[:media_upload].nil?
@parameter_order = args[:parameter_order] unless args[:parameter_order].nil?
@parameters = args[:parameters] unless args[:parameters].nil?
@path = args[:path] unless args[:path].nil?
@request = args[:request] unless args[:request].nil?
@response = args[:response] unless args[:response].nil?
@scopes = args[:scopes] unless args[:scopes].nil?
@supports_media_download = args[:supports_media_download] unless args[:supports_media_download].nil?
@supports_media_upload = args[:supports_media_upload] unless args[:supports_media_upload].nil?
@supports_subscription = args[:supports_subscription] unless args[:supports_subscription].nil?
@use_media_download_service = args[:use_media_download_service] unless args[:use_media_download_service].nil?
end
# Media upload parameters.
class MediaUpload
include Google::Apis::Core::Hashable
# MIME Media Ranges for acceptable media uploads to this method.
# Corresponds to the JSON property `accept`
# @return [Array<String>]
attr_accessor :accept
# Maximum size of a media upload, such as "1MB", "2GB" or "3TB".
# Corresponds to the JSON property `maxSize`
# @return [String]
attr_accessor :max_size
# Supported upload protocols.
# Corresponds to the JSON property `protocols`
# @return [Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols]
attr_accessor :protocols
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@accept = args[:accept] unless args[:accept].nil?
@max_size = args[:max_size] unless args[:max_size].nil?
@protocols = args[:protocols] unless args[:protocols].nil?
end
# Supported upload protocols.
class Protocols
include Google::Apis::Core::Hashable
# Supports the Resumable Media Upload protocol.
# Corresponds to the JSON property `resumable`
# @return [Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Resumable]
attr_accessor :resumable
# Supports uploading as a single HTTP request.
# Corresponds to the JSON property `simple`
# @return [Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Simple]
attr_accessor :simple
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@resumable = args[:resumable] unless args[:resumable].nil?
@simple = args[:simple] unless args[:simple].nil?
end
# Supports the Resumable Media Upload protocol.
class Resumable
include Google::Apis::Core::Hashable
# True if this endpoint supports uploading multipart media.
# Corresponds to the JSON property `multipart`
# @return [Boolean]
attr_accessor :multipart
alias_method :multipart?, :multipart
# The URI path to be used for upload. Should be used in conjunction with the
# basePath property at the api-level.
# Corresponds to the JSON property `path`
# @return [String]
attr_accessor :path
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@multipart = args[:multipart] unless args[:multipart].nil?
@path = args[:path] unless args[:path].nil?
end
end
# Supports uploading as a single HTTP request.
class Simple
include Google::Apis::Core::Hashable
# True if this endpoint supports upload multipart media.
# Corresponds to the JSON property `multipart`
# @return [Boolean]
attr_accessor :multipart
alias_method :multipart?, :multipart
# The URI path to be used for upload. Should be used in conjunction with the
# basePath property at the api-level.
# Corresponds to the JSON property `path`
# @return [String]
attr_accessor :path
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@multipart = args[:multipart] unless args[:multipart].nil?
@path = args[:path] unless args[:path].nil?
end
end
end
end
# The schema for the request.
class Request
include Google::Apis::Core::Hashable
# Schema ID for the request schema.
# Corresponds to the JSON property `$ref`
# @return [String]
attr_accessor :_ref
# parameter name.
# Corresponds to the JSON property `parameterName`
# @return [String]
attr_accessor :parameter_name
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@_ref = args[:_ref] unless args[:_ref].nil?
@parameter_name = args[:parameter_name] unless args[:parameter_name].nil?
end
end
# The schema for the response.
class Response
include Google::Apis::Core::Hashable
# Schema ID for the response schema.
# Corresponds to the JSON property `$ref`
# @return [String]
attr_accessor :_ref
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@_ref = args[:_ref] unless args[:_ref].nil?
end
end
end
#
class RestResource
include Google::Apis::Core::Hashable
# Methods on this resource.
# Corresponds to the JSON property `methods`
# @return [Hash<String,Google::Apis::DiscoveryV1::RestMethod>]
attr_accessor :api_methods
# Sub-resources on this resource.
# Corresponds to the JSON property `resources`
# @return [Hash<String,Google::Apis::DiscoveryV1::RestResource>]
attr_accessor :resources
def initialize(**args)
update!(**args)
end
# Update properties of this object
def update!(**args)
@api_methods = args[:api_methods] unless args[:api_methods].nil?
@resources = args[:resources] unless args[:resources].nil?
end
end
end
end
end

View File

@ -0,0 +1,355 @@
# Copyright 2015 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 'date'
require 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
module Google
module Apis
module DiscoveryV1
class DirectoryList
class Representation < Google::Apis::Core::JsonRepresentation; end
class Item
class Representation < Google::Apis::Core::JsonRepresentation; end
class Icons
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
end
class JsonSchema
class Representation < Google::Apis::Core::JsonRepresentation; end
class Annotations
class Representation < Google::Apis::Core::JsonRepresentation; end
end
class Variant
class Representation < Google::Apis::Core::JsonRepresentation; end
class Map
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
end
class RestDescription
class Representation < Google::Apis::Core::JsonRepresentation; end
class Auth
class Representation < Google::Apis::Core::JsonRepresentation; end
class Oauth2
class Representation < Google::Apis::Core::JsonRepresentation; end
class Scope
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
end
class Icons
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
class RestMethod
class Representation < Google::Apis::Core::JsonRepresentation; end
class MediaUpload
class Representation < Google::Apis::Core::JsonRepresentation; end
class Protocols
class Representation < Google::Apis::Core::JsonRepresentation; end
class Resumable
class Representation < Google::Apis::Core::JsonRepresentation; end
end
class Simple
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
end
class Request
class Representation < Google::Apis::Core::JsonRepresentation; end
end
class Response
class Representation < Google::Apis::Core::JsonRepresentation; end
end
end
class RestResource
class Representation < Google::Apis::Core::JsonRepresentation; end
end
# @private
class DirectoryList
class Representation < Google::Apis::Core::JsonRepresentation
property :discovery_version, as: 'discoveryVersion'
collection :items, as: 'items', class: Google::Apis::DiscoveryV1::DirectoryList::Item, decorator: Google::Apis::DiscoveryV1::DirectoryList::Item::Representation
property :kind, as: 'kind'
end
# @private
class Item
class Representation < Google::Apis::Core::JsonRepresentation
property :description, as: 'description'
property :discovery_link, as: 'discoveryLink'
property :discovery_rest_url, as: 'discoveryRestUrl'
property :documentation_link, as: 'documentationLink'
property :icons, as: 'icons', class: Google::Apis::DiscoveryV1::DirectoryList::Item::Icons, decorator: Google::Apis::DiscoveryV1::DirectoryList::Item::Icons::Representation
property :id, as: 'id'
property :kind, as: 'kind'
collection :labels, as: 'labels'
property :name, as: 'name'
property :preferred, as: 'preferred'
property :title, as: 'title'
property :version, as: 'version'
end
# @private
class Icons
class Representation < Google::Apis::Core::JsonRepresentation
property :x16, as: 'x16'
property :x32, as: 'x32'
end
end
end
end
# @private
class JsonSchema
class Representation < Google::Apis::Core::JsonRepresentation
property :_ref, as: '$ref'
property :additional_properties, as: 'additionalProperties', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :annotations, as: 'annotations', class: Google::Apis::DiscoveryV1::JsonSchema::Annotations, decorator: Google::Apis::DiscoveryV1::JsonSchema::Annotations::Representation
property :default, as: 'default'
property :description, as: 'description'
collection :enum, as: 'enum'
collection :enum_descriptions, as: 'enumDescriptions'
property :format, as: 'format'
property :id, as: 'id'
property :items, as: 'items', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :location, as: 'location'
property :maximum, as: 'maximum'
property :minimum, as: 'minimum'
property :pattern, as: 'pattern'
hash :properties, as: 'properties', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :read_only, as: 'readOnly'
property :repeated, as: 'repeated'
property :required, as: 'required'
property :type, as: 'type'
property :variant, as: 'variant', class: Google::Apis::DiscoveryV1::JsonSchema::Variant, decorator: Google::Apis::DiscoveryV1::JsonSchema::Variant::Representation
end
# @private
class Annotations
class Representation < Google::Apis::Core::JsonRepresentation
collection :required, as: 'required'
end
end
# @private
class Variant
class Representation < Google::Apis::Core::JsonRepresentation
property :discriminant, as: 'discriminant'
collection :map, as: 'map', class: Google::Apis::DiscoveryV1::JsonSchema::Variant::Map, decorator: Google::Apis::DiscoveryV1::JsonSchema::Variant::Map::Representation
end
# @private
class Map
class Representation < Google::Apis::Core::JsonRepresentation
property :_ref, as: '$ref'
property :type_value, as: 'type_value'
end
end
end
end
# @private
class RestDescription
class Representation < Google::Apis::Core::JsonRepresentation
property :auth, as: 'auth', class: Google::Apis::DiscoveryV1::RestDescription::Auth, decorator: Google::Apis::DiscoveryV1::RestDescription::Auth::Representation
property :base_path, as: 'basePath'
property :base_url, as: 'baseUrl'
property :batch_path, as: 'batchPath'
property :canonical_name, as: 'canonicalName'
property :description, as: 'description'
property :discovery_version, as: 'discoveryVersion'
property :documentation_link, as: 'documentationLink'
property :etag, as: 'etag'
collection :features, as: 'features'
property :icons, as: 'icons', class: Google::Apis::DiscoveryV1::RestDescription::Icons, decorator: Google::Apis::DiscoveryV1::RestDescription::Icons::Representation
property :id, as: 'id'
property :kind, as: 'kind'
collection :labels, as: 'labels'
hash :api_methods, as: 'methods', class: Google::Apis::DiscoveryV1::RestMethod, decorator: Google::Apis::DiscoveryV1::RestMethod::Representation
property :name, as: 'name'
property :owner_domain, as: 'ownerDomain'
property :owner_name, as: 'ownerName'
property :package_path, as: 'packagePath'
hash :parameters, as: 'parameters', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :protocol, as: 'protocol'
hash :resources, as: 'resources', class: Google::Apis::DiscoveryV1::RestResource, decorator: Google::Apis::DiscoveryV1::RestResource::Representation
property :revision, as: 'revision'
property :root_url, as: 'rootUrl'
hash :schemas, as: 'schemas', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :service_path, as: 'servicePath'
property :title, as: 'title'
property :version, as: 'version'
end
# @private
class Auth
class Representation < Google::Apis::Core::JsonRepresentation
property :oauth2, as: 'oauth2', class: Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2, decorator: Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2::Representation
end
# @private
class Oauth2
class Representation < Google::Apis::Core::JsonRepresentation
hash :scopes, as: 'scopes', class: Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2::Scope, decorator: Google::Apis::DiscoveryV1::RestDescription::Auth::Oauth2::Scope::Representation
end
# @private
class Scope
class Representation < Google::Apis::Core::JsonRepresentation
property :description, as: 'description'
end
end
end
end
# @private
class Icons
class Representation < Google::Apis::Core::JsonRepresentation
property :x16, as: 'x16'
property :x32, as: 'x32'
end
end
end
# @private
class RestMethod
class Representation < Google::Apis::Core::JsonRepresentation
property :description, as: 'description'
property :etag_required, as: 'etagRequired'
property :http_method, as: 'httpMethod'
property :id, as: 'id'
property :media_upload, as: 'mediaUpload', class: Google::Apis::DiscoveryV1::RestMethod::MediaUpload, decorator: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Representation
collection :parameter_order, as: 'parameterOrder'
hash :parameters, as: 'parameters', class: Google::Apis::DiscoveryV1::JsonSchema, decorator: Google::Apis::DiscoveryV1::JsonSchema::Representation
property :path, as: 'path'
property :request, as: 'request', class: Google::Apis::DiscoveryV1::RestMethod::Request, decorator: Google::Apis::DiscoveryV1::RestMethod::Request::Representation
property :response, as: 'response', class: Google::Apis::DiscoveryV1::RestMethod::Response, decorator: Google::Apis::DiscoveryV1::RestMethod::Response::Representation
collection :scopes, as: 'scopes'
property :supports_media_download, as: 'supportsMediaDownload'
property :supports_media_upload, as: 'supportsMediaUpload'
property :supports_subscription, as: 'supportsSubscription'
property :use_media_download_service, as: 'useMediaDownloadService'
end
# @private
class MediaUpload
class Representation < Google::Apis::Core::JsonRepresentation
collection :accept, as: 'accept'
property :max_size, as: 'maxSize'
property :protocols, as: 'protocols', class: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols, decorator: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Representation
end
# @private
class Protocols
class Representation < Google::Apis::Core::JsonRepresentation
property :resumable, as: 'resumable', class: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Resumable, decorator: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Resumable::Representation
property :simple, as: 'simple', class: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Simple, decorator: Google::Apis::DiscoveryV1::RestMethod::MediaUpload::Protocols::Simple::Representation
end
# @private
class Resumable
class Representation < Google::Apis::Core::JsonRepresentation
property :multipart, as: 'multipart'
property :path, as: 'path'
end
end
# @private
class Simple
class Representation < Google::Apis::Core::JsonRepresentation
property :multipart, as: 'multipart'
property :path, as: 'path'
end
end
end
end
# @private
class Request
class Representation < Google::Apis::Core::JsonRepresentation
property :_ref, as: '$ref'
property :parameter_name, as: 'parameterName'
end
end
# @private
class Response
class Representation < Google::Apis::Core::JsonRepresentation
property :_ref, as: '$ref'
end
end
end
# @private
class RestResource
class Representation < Google::Apis::Core::JsonRepresentation
hash :api_methods, as: 'methods', class: Google::Apis::DiscoveryV1::RestMethod, decorator: Google::Apis::DiscoveryV1::RestMethod::Representation
hash :resources, as: 'resources', class: Google::Apis::DiscoveryV1::RestResource, decorator: Google::Apis::DiscoveryV1::RestResource::Representation
end
end
end
end
end

View File

@ -0,0 +1,144 @@
# Copyright 2015 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 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
module Google
module Apis
module DiscoveryV1
# APIs Discovery Service
#
# Lets you discover information about other Google APIs, such as what APIs are
# available, the resource and method details for each API.
#
# @example
# require 'google/apis/discovery_v1'
#
# Discovery = Google::Apis::DiscoveryV1 # Alias the module
# service = Discovery::DiscoveryService.new
#
# @see https://developers.google.com/discovery/
class DiscoveryService < Google::Apis::Core::BaseService
# @return [String]
# API key. Your API key identifies your project and provides you with API access,
# quota, and reports. Required unless you provide an OAuth 2.0 token.
attr_accessor :key
# @return [String]
# Available to use for quota purposes for server-side applications. Can be any
# arbitrary string assigned to a user, but should not exceed 40 characters.
# Overrides userIp if both are provided.
attr_accessor :quota_user
# @return [String]
# IP address of the site where the request originates. Use this if you want to
# enforce per-user limits.
attr_accessor :user_ip
def initialize
super('https://www.googleapis.com/', 'discovery/v1/')
end
# Retrieve the description of a particular version of an api.
# @param [String] api
# The name of the API.
# @param [String] version
# The version of the API.
# @param [String] fields
# Selector specifying which fields to include in a partial response.
# @param [String] quota_user
# Available to use for quota purposes for server-side applications. Can be any
# arbitrary string assigned to a user, but should not exceed 40 characters.
# Overrides userIp if both are provided.
# @param [String] user_ip
# IP address of the site where the request originates. Use this if you want to
# enforce per-user limits.
# @param [Google::Apis::RequestOptions] options
# Request-specific options
#
# @yield [result, err] Result & error if block supplied
# @yieldparam result [Google::Apis::DiscoveryV1::RestDescription] parsed result object
# @yieldparam err [StandardError] error object if request failed
#
# @return [Google::Apis::DiscoveryV1::RestDescription]
#
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def get_rest_api(api, version, fields: nil, quota_user: nil, user_ip: nil, options: nil, &block)
path = 'apis/{api}/{version}/rest'
command = make_simple_command(:get, path, options)
command.response_representation = Google::Apis::DiscoveryV1::RestDescription::Representation
command.response_class = Google::Apis::DiscoveryV1::RestDescription
command.params['api'] = api unless api.nil?
command.params['version'] = version unless version.nil?
command.query['fields'] = fields unless fields.nil?
command.query['quotaUser'] = quota_user unless quota_user.nil?
command.query['userIp'] = user_ip unless user_ip.nil?
execute_or_queue_command(command, &block)
end
# Retrieve the list of APIs supported at this endpoint.
# @param [String] name
# Only include APIs with the given name.
# @param [Boolean] preferred
# Return only the preferred version of an API.
# @param [String] fields
# Selector specifying which fields to include in a partial response.
# @param [String] quota_user
# Available to use for quota purposes for server-side applications. Can be any
# arbitrary string assigned to a user, but should not exceed 40 characters.
# Overrides userIp if both are provided.
# @param [String] user_ip
# IP address of the site where the request originates. Use this if you want to
# enforce per-user limits.
# @param [Google::Apis::RequestOptions] options
# Request-specific options
#
# @yield [result, err] Result & error if block supplied
# @yieldparam result [Google::Apis::DiscoveryV1::DirectoryList] parsed result object
# @yieldparam err [StandardError] error object if request failed
#
# @return [Google::Apis::DiscoveryV1::DirectoryList]
#
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def list_apis(name: nil, preferred: nil, fields: nil, quota_user: nil, user_ip: nil, options: nil, &block)
path = 'apis'
command = make_simple_command(:get, path, options)
command.response_representation = Google::Apis::DiscoveryV1::DirectoryList::Representation
command.response_class = Google::Apis::DiscoveryV1::DirectoryList
command.query['name'] = name unless name.nil?
command.query['preferred'] = preferred unless preferred.nil?
command.query['fields'] = fields unless fields.nil?
command.query['quotaUser'] = quota_user unless quota_user.nil?
command.query['userIp'] = user_ip unless user_ip.nil?
execute_or_queue_command(command, &block)
end
protected
def apply_command_defaults(command)
command.query['key'] = key unless key.nil?
command.query['quotaUser'] = quota_user unless quota_user.nil?
command.query['userIp'] = user_ip unless user_ip.nil?
end
end
end
end
end

View File

@ -1,43 +1,31 @@
# -*- encoding: utf-8 -*- # coding: utf-8
require File.join(File.dirname(__FILE__), 'lib/google/api_client', 'version') lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'google/apis/version'
Gem::Specification.new do |s| Gem::Specification.new do |spec|
s.name = "google-api-client" spec.name = 'google-api-client'
s.version = Google::APIClient::VERSION::STRING spec.version = Google::Apis::VERSION
spec.authors = ['Steven Bazyl', 'Tim Emiola', 'Sergio Gomes', 'Bob Aman']
spec.email = ['sbazyl@google.com']
spec.summary = %q{Client for accessing Google APIs}
spec.homepage = 'https://github.com/google/google-api-ruby-client'
spec.license = 'Apache 2.0'
s.required_rubygems_version = ">= 1.3.5" spec.files = `git ls-files -z`.split("\x0")
s.require_paths = ["lib"] spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
s.authors = ["Bob Aman", "Steven Bazyl"] spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
s.license = "Apache-2.0" spec.require_paths = ['lib', 'generated']
s.description = "The Google API Ruby Client makes it trivial to discover and access supported APIs."
s.email = "sbazyl@google.com"
s.extra_rdoc_files = ["README.md"]
s.files = %w(google-api-client.gemspec Rakefile LICENSE CHANGELOG.md README.md Gemfile)
s.files += Dir.glob("lib/**/*.rb")
s.files += Dir.glob("lib/cacerts.pem")
s.files += Dir.glob("spec/**/*.{rb,opts}")
s.files += Dir.glob("vendor/**/*.rb")
s.files += Dir.glob("tasks/**/*")
s.files += Dir.glob("website/**/*")
s.homepage = "https://github.com/google/google-api-ruby-client/"
s.rdoc_options = ["--main", "README.md"]
s.summary = "The Google API Ruby Client makes it trivial to discover and access Google's REST APIs."
s.add_runtime_dependency 'addressable', '~> 2.3' spec.add_runtime_dependency 'representable', '~> 2.1'
s.add_runtime_dependency 'signet', '~> 0.6' spec.add_runtime_dependency 'multi_json', '~> 1.11'
s.add_runtime_dependency 'faraday', '~> 0.9' spec.add_runtime_dependency 'retriable', '~> 2.0'
s.add_runtime_dependency 'googleauth', '~> 0.3' spec.add_runtime_dependency 'activesupport', '>= 3.2'
s.add_runtime_dependency 'multi_json', '~> 1.10' spec.add_runtime_dependency 'addressable', '~> 2.3'
s.add_runtime_dependency 'autoparse', '~> 0.3' spec.add_runtime_dependency 'mime-types', '>= 1.6'
s.add_runtime_dependency 'extlib', '~> 0.9' spec.add_runtime_dependency 'hurley', '~> 0.1'
s.add_runtime_dependency 'launchy', '~> 2.4' spec.add_runtime_dependency 'googleauth', '~> 0.2'
s.add_runtime_dependency 'retriable', '~> 1.4' spec.add_runtime_dependency 'thor', '~> 0.19'
s.add_runtime_dependency 'activesupport', '>= 3.2' spec.add_runtime_dependency 'memoist', '~> 0.11'
spec.add_runtime_dependency 'virtus', '~> 1.0'
s.add_development_dependency 'rake', '~> 10.0'
s.add_development_dependency 'yard', '~> 0.8'
s.add_development_dependency 'rspec', '~> 3.1'
s.add_development_dependency 'kramdown', '~> 1.5'
s.add_development_dependency 'simplecov', '~> 0.9.2'
s.add_development_dependency 'coveralls', '~> 0.7.11'
end end

View File

@ -1,19 +0,0 @@
require 'multi_json'
if !MultiJson.respond_to?(:load) || [
Kernel,
defined?(ActiveSupport::Dependencies::Loadable) && ActiveSupport::Dependencies::Loadable
].compact.include?(MultiJson.method(:load).owner)
module MultiJson
class <<self
alias :load :decode
end
end
end
if !MultiJson.respond_to?(:dump)
module MultiJson
class <<self
alias :dump :encode
end
end
end

View File

@ -1,750 +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.
require 'faraday'
require 'multi_json'
require 'compat/multi_json'
require 'stringio'
require 'retriable'
require 'google/api_client/version'
require 'google/api_client/logging'
require 'google/api_client/errors'
require 'google/api_client/environment'
require 'google/api_client/discovery'
require 'google/api_client/request'
require 'google/api_client/reference'
require 'google/api_client/result'
require 'google/api_client/media'
require 'google/api_client/service_account'
require 'google/api_client/batch'
require 'google/api_client/gzip'
require 'google/api_client/charset'
require 'google/api_client/client_secrets'
require 'google/api_client/railtie' if defined?(Rails)
module Google
##
# This class manages APIs communication.
class APIClient
include Google::APIClient::Logging
##
# Creates a new Google API client.
#
# @param [Hash] options The configuration parameters for the client.
# @option options [Symbol, #generate_authenticated_request] :authorization
# (:oauth_1)
# The authorization mechanism used by the client. The following
# mechanisms are supported out-of-the-box:
# <ul>
# <li><code>:two_legged_oauth_1</code></li>
# <li><code>:oauth_1</code></li>
# <li><code>:oauth_2</code></li>
# <li><code>:google_app_default</code></li>
# </ul>
# @option options [Boolean] :auto_refresh_token (true)
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute. If the token does
# not support it, this option is ignored.
# @option options [String] :application_name
# The name of the application using the client.
# @option options [String | Array | nil] :scope
# The scope(s) used when using google application default credentials
# @option options [String] :application_version
# The version number of the application using the client.
# @option options [String] :user_agent
# ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
# The user agent used by the client. Most developers will want to
# leave this value alone and use the `:application_name` option instead.
# @option options [String] :host ("www.googleapis.com")
# The API hostname used by the client. This rarely needs to be changed.
# @option options [String] :port (443)
# The port number used by the client. This rarely needs to be changed.
# @option options [String] :discovery_path ("/discovery/v1")
# The discovery base path. This rarely needs to be changed.
# @option options [String] :ca_file
# Optional set of root certificates to use when validating SSL connections.
# By default, a bundled set of trusted roots will be used.
# @options options[Hash] :force_encoding
# Experimental option. True if response body should be force encoded into the charset
# specified in the Content-Type header. Mostly intended for compressed content.
# @options options[Hash] :faraday_option
# Pass through of options to set on the Faraday connection
def initialize(options={})
logger.debug { "#{self.class} - Initializing client with options #{options}" }
# Normalize key to String to allow indifferent access.
options = options.inject({}) do |accu, (key, value)|
accu[key.to_sym] = value
accu
end
# Almost all API usage will have a host of 'www.googleapis.com'.
self.host = options[:host] || 'www.googleapis.com'
self.port = options[:port] || 443
self.discovery_path = options[:discovery_path] || '/discovery/v1'
# Most developers will want to leave this value alone and use the
# application_name option.
if options[:application_name]
app_name = options[:application_name]
app_version = options[:application_version]
application_string = "#{app_name}/#{app_version || '0.0.0'}"
else
logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
end
proxy = options[:proxy] || Object::ENV["http_proxy"]
self.user_agent = options[:user_agent] || (
"#{application_string} " +
"google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)"
).strip
# The writer method understands a few Symbols and will generate useful
# default authentication mechanisms.
self.authorization =
options.key?(:authorization) ? options[:authorization] : :oauth_2
if !options['scope'].nil? and self.authorization.respond_to?(:scope=)
self.authorization.scope = options['scope']
end
self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
self.key = options[:key]
self.user_ip = options[:user_ip]
self.retries = options.fetch(:retries) { 0 }
self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
@discovery_uris = {}
@discovery_documents = {}
@discovered_apis = {}
ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
self.connection = Faraday.new do |faraday|
faraday.response :charset if options[:force_encoding]
faraday.response :gzip
faraday.options.params_encoder = Faraday::FlatParamsEncoder
faraday.ssl.ca_file = ca_file
faraday.ssl.verify = true
faraday.proxy proxy
faraday.adapter Faraday.default_adapter
if options[:faraday_option].is_a?(Hash)
options[:faraday_option].each_pair do |option, value|
faraday.options.send("#{option}=", value)
end
end
end
return self
end
##
# Returns the authorization mechanism used by the client.
#
# @return [#generate_authenticated_request] The authorization mechanism.
attr_reader :authorization
##
# Sets the authorization mechanism used by the client.
#
# @param [#generate_authenticated_request] new_authorization
# The new authorization mechanism.
def authorization=(new_authorization)
case new_authorization
when :oauth_1, :oauth
require 'signet/oauth_1/client'
# NOTE: Do not rely on this default value, as it may change
new_authorization = Signet::OAuth1::Client.new(
:temporary_credential_uri =>
'https://www.google.com/accounts/OAuthGetRequestToken',
:authorization_uri =>
'https://www.google.com/accounts/OAuthAuthorizeToken',
:token_credential_uri =>
'https://www.google.com/accounts/OAuthGetAccessToken',
:client_credential_key => 'anonymous',
:client_credential_secret => 'anonymous'
)
when :two_legged_oauth_1, :two_legged_oauth
require 'signet/oauth_1/client'
# NOTE: Do not rely on this default value, as it may change
new_authorization = Signet::OAuth1::Client.new(
:client_credential_key => nil,
:client_credential_secret => nil,
:two_legged => true
)
when :google_app_default
require 'googleauth'
new_authorization = Google::Auth.get_application_default
when :oauth_2
require 'signet/oauth_2/client'
# NOTE: Do not rely on this default value, as it may change
new_authorization = Signet::OAuth2::Client.new(
:authorization_uri =>
'https://accounts.google.com/o/oauth2/auth',
:token_credential_uri =>
'https://accounts.google.com/o/oauth2/token'
)
when nil
# No authorization mechanism
else
if !new_authorization.respond_to?(:generate_authenticated_request)
raise TypeError,
'Expected authorization mechanism to respond to ' +
'#generate_authenticated_request.'
end
end
@authorization = new_authorization
return @authorization
end
##
# Default Faraday/HTTP connection.
#
# @return [Faraday::Connection]
attr_accessor :connection
##
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute.
#
# @return [Boolean]
attr_accessor :auto_refresh_token
##
# The application's API key issued by the API console.
#
# @return [String] The API key.
attr_accessor :key
##
# The IP address of the user this request is being performed on behalf of.
#
# @return [String] The user's IP address.
attr_accessor :user_ip
##
# The user agent used by the client.
#
# @return [String]
# The user agent string used in the User-Agent header.
attr_accessor :user_agent
##
# The API hostname used by the client.
#
# @return [String]
# The API hostname. Should almost always be 'www.googleapis.com'.
attr_accessor :host
##
# The port number used by the client.
#
# @return [String]
# The port number. Should almost always be 443.
attr_accessor :port
##
# The base path used by the client for discovery.
#
# @return [String]
# The base path. Should almost always be '/discovery/v1'.
attr_accessor :discovery_path
##
# Number of times to retry on recoverable errors
#
# @return [FixNum]
# Number of retries
attr_accessor :retries
##
# Whether or not an expired auth token should be re-acquired
# (and the operation retried) regardless of retries setting
# @return [Boolean]
# Auto retry on auth expiry
attr_accessor :expired_auth_retry
##
# Returns the URI for the directory document.
#
# @return [Addressable::URI] The URI of the directory document.
def directory_uri
return resolve_uri(self.discovery_path + '/apis')
end
##
# Manually registers a URI as a discovery document for a specific version
# of an API.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @param [Addressable::URI] uri The URI of the discovery document.
# @return [Google::APIClient::API] The service object.
def register_discovery_uri(api, version, uri)
api = api.to_s
version = version || 'v1'
@discovery_uris["#{api}:#{version}"] = uri
discovered_api(api, version)
end
##
# Returns the URI for the discovery document.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @return [Addressable::URI] The URI of the discovery document.
def discovery_uri(api, version=nil)
api = api.to_s
version = version || 'v1'
return @discovery_uris["#{api}:#{version}"] ||= (
resolve_uri(
self.discovery_path + '/apis/{api}/{version}/rest',
'api' => api,
'version' => version
)
)
end
##
# Manually registers a pre-loaded discovery document for a specific version
# of an API.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @param [String, StringIO] discovery_document
# The contents of the discovery document.
# @return [Google::APIClient::API] The service object.
def register_discovery_document(api, version, discovery_document)
api = api.to_s
version = version || 'v1'
if discovery_document.kind_of?(StringIO)
discovery_document.rewind
discovery_document = discovery_document.string
elsif discovery_document.respond_to?(:to_str)
discovery_document = discovery_document.to_str
else
raise TypeError,
"Expected String or StringIO, got #{discovery_document.class}."
end
@discovery_documents["#{api}:#{version}"] =
MultiJson.load(discovery_document)
discovered_api(api, version)
end
##
# Returns the parsed directory document.
#
# @return [Hash] The parsed JSON from the directory document.
def directory_document
return @directory_document ||= (begin
response = self.execute!(
:http_method => :get,
:uri => self.directory_uri,
:authenticated => false
)
response.data
end)
end
##
# Returns the parsed discovery document.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @return [Hash] The parsed JSON from the discovery document.
def discovery_document(api, version=nil)
api = api.to_s
version = version || 'v1'
return @discovery_documents["#{api}:#{version}"] ||= (begin
response = self.execute!(
:http_method => :get,
:uri => self.discovery_uri(api, version),
:authenticated => false
)
response.data
end)
end
##
# Returns all APIs published in the directory document.
#
# @return [Array] The list of available APIs.
def discovered_apis
@directory_apis ||= (begin
document_base = self.directory_uri
if self.directory_document && self.directory_document['items']
self.directory_document['items'].map do |discovery_document|
Google::APIClient::API.new(
document_base,
discovery_document
)
end
else
[]
end
end)
end
##
# Returns the service object for a given service name and service version.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
#
# @return [Google::APIClient::API] The service object.
def discovered_api(api, version=nil)
if !api.kind_of?(String) && !api.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{api.class}."
end
api = api.to_s
version = version || 'v1'
return @discovered_apis["#{api}:#{version}"] ||= begin
document_base = self.discovery_uri(api, version)
discovery_document = self.discovery_document(api, version)
if document_base && discovery_document
Google::APIClient::API.new(
document_base,
discovery_document
)
else
nil
end
end
end
##
# Returns the method object for a given RPC name and service version.
#
# @param [String, Symbol] rpc_name The RPC name of the desired method.
# @param [String, Symbol] api The API the method is within.
# @param [String] version The desired version of the API.
#
# @return [Google::APIClient::Method] The method object.
def discovered_method(rpc_name, api, version=nil)
if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{rpc_name.class}."
end
rpc_name = rpc_name.to_s
api = api.to_s
version = version || 'v1'
service = self.discovered_api(api, version)
if service.to_h[rpc_name]
return service.to_h[rpc_name]
else
return nil
end
end
##
# Returns the service object with the highest version number.
#
# @note <em>Warning</em>: This method should be used with great care.
# As APIs are updated, minor differences between versions may cause
# incompatibilities. Requesting a specific version will avoid this issue.
#
# @param [String, Symbol] api The name of the service.
#
# @return [Google::APIClient::API] The service object.
def preferred_version(api)
if !api.kind_of?(String) && !api.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{api.class}."
end
api = api.to_s
return self.discovered_apis.detect do |a|
a.name == api && a.preferred == true
end
end
##
# Verifies an ID token against a server certificate. Used to ensure that
# an ID token supplied by an untrusted client-side mechanism is valid.
# Raises an error if the token is invalid or missing.
#
# @deprecated Use the google-id-token gem for verifying JWTs
def verify_id_token!
require 'jwt'
require 'openssl'
@certificates ||= {}
if !self.authorization.respond_to?(:id_token)
raise ArgumentError, (
"Current authorization mechanism does not support ID tokens: " +
"#{self.authorization.class.to_s}"
)
elsif !self.authorization.id_token
raise ArgumentError, (
"Could not verify ID token, ID token missing. " +
"Scopes were: #{self.authorization.scope.inspect}"
)
else
check_cached_certs = lambda do
valid = false
for _key, cert in @certificates
begin
self.authorization.decoded_id_token(cert.public_key)
valid = true
rescue JWT::DecodeError, Signet::UnsafeOperationError
# Expected exception. Ignore, ID token has not been validated.
end
end
valid
end
if check_cached_certs.call()
return true
end
response = self.execute!(
:http_method => :get,
:uri => 'https://www.googleapis.com/oauth2/v1/certs',
:authenticated => false
)
@certificates.merge!(
Hash[MultiJson.load(response.body).map do |key, cert|
[key, OpenSSL::X509::Certificate.new(cert)]
end]
)
if check_cached_certs.call()
return true
else
raise InvalidIDTokenError,
"Could not verify ID token against any available certificate."
end
end
return nil
end
##
# Generates a request.
#
# @option options [Google::APIClient::Method] :api_method
# The method object or the RPC name of the method being executed.
# @option options [Hash, Array] :parameters
# The parameters to send to the method.
# @option options [Hash, Array] :headers The HTTP headers for the request.
# @option options [String] :body The body of the request.
# @option options [String] :version ("v1")
# The service version. Only used if `api_method` is a `String`.
# @option options [#generate_authenticated_request] :authorization
# The authorization mechanism for the response. Used only if
# `:authenticated` is `true`.
# @option options [TrueClass, FalseClass] :authenticated (true)
# `true` if the request must be signed or somehow
# authenticated, `false` otherwise.
#
# @return [Google::APIClient::Reference] The generated request.
#
# @example
# request = client.generate_request(
# :api_method => 'plus.activities.list',
# :parameters =>
# {'collection' => 'public', 'userId' => 'me'}
# )
def generate_request(options={})
options = {
:api_client => self
}.merge(options)
return Google::APIClient::Request.new(options)
end
##
# Executes a request, wrapping it in a Result object.
#
# @param [Google::APIClient::Request, Hash, Array] params
# Either a Google::APIClient::Request, a Hash, or an Array.
#
# If a Google::APIClient::Request, no other parameters are expected.
#
# If a Hash, the below parameters are handled. If an Array, the
# parameters are assumed to be in the below order:
#
# - (Google::APIClient::Method) api_method:
# The method object or the RPC name of the method being executed.
# - (Hash, Array) parameters:
# The parameters to send to the method.
# - (String) body: The body of the request.
# - (Hash, Array) headers: The HTTP headers for the request.
# - (Hash) options: A set of options for the request, of which:
# - (#generate_authenticated_request) :authorization (default: true) -
# The authorization mechanism for the response. Used only if
# `:authenticated` is `true`.
# - (TrueClass, FalseClass) :authenticated (default: true) -
# `true` if the request must be signed or somehow
# authenticated, `false` otherwise.
# - (TrueClass, FalseClass) :gzip (default: true) -
# `true` if gzip enabled, `false` otherwise.
# - (FixNum) :retries -
# # of times to retry on recoverable errors
#
# @return [Google::APIClient::Result] The result from the API, nil if batch.
#
# @example
# result = client.execute(batch_request)
#
# @example
# plus = client.discovered_api('plus')
# result = client.execute(
# :api_method => plus.activities.list,
# :parameters => {'collection' => 'public', 'userId' => 'me'}
# )
#
# @see Google::APIClient#generate_request
def execute!(*params)
if params.first.kind_of?(Google::APIClient::Request)
request = params.shift
options = params.shift || {}
else
# This block of code allows us to accept multiple parameter passing
# styles, and maintaining some backwards compatibility.
#
# Note: I'm extremely tempted to deprecate this style of execute call.
if params.last.respond_to?(:to_hash) && params.size == 1
options = params.pop
else
options = {}
end
options[:api_method] = params.shift if params.size > 0
options[:parameters] = params.shift if params.size > 0
options[:body] = params.shift if params.size > 0
options[:headers] = params.shift if params.size > 0
options.update(params.shift) if params.size > 0
request = self.generate_request(options)
end
request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
request.headers['Content-Type'] ||= ''
request.parameters['key'] ||= self.key unless self.key.nil?
request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
connection = options[:connection] || self.connection
request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
tries = 1 + (options[:retries] || self.retries)
attempt = 0
Retriable.retriable :tries => tries,
:on => [TransmissionError],
:on_retry => client_error_handler,
:interval => lambda {|attempts| (2 ** attempts) + rand} do
attempt += 1
# This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
# auth to be re-attempted without having to retry all sorts of other failures like
# NotFound, etc
Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1,
:on => [AuthorizationError],
:on_retry => authorization_error_handler(request.authorization) do
result = request.send(connection, true)
case result.status
when 200...300
result
when 301, 302, 303, 307
request = generate_request(request.to_hash.merge({
:uri => result.headers['location'],
:api_method => nil
}))
raise RedirectError.new(result.headers['location'], result)
when 401
raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
when 400, 402...500
raise ClientError.new(result.error_message || "A client error has occurred", result)
when 500...600
raise ServerError.new(result.error_message || "A server error has occurred", result)
else
raise TransmissionError.new(result.error_message || "A transmission error has occurred", result)
end
end
end
end
##
# Same as Google::APIClient#execute!, but does not raise an exception for
# normal API errros.
#
# @see Google::APIClient#execute
def execute(*params)
begin
return self.execute!(*params)
rescue TransmissionError => e
return e.result
end
end
protected
##
# Resolves a URI template against the client's configured base.
#
# @api private
# @param [String, Addressable::URI, Addressable::Template] template
# The template to resolve.
# @param [Hash] mapping The mapping that corresponds to the template.
# @return [Addressable::URI] The expanded URI.
def resolve_uri(template, mapping={})
@base_uri ||= Addressable::URI.new(
:scheme => 'https',
:host => self.host,
:port => self.port
).normalize
template = if template.kind_of?(Addressable::Template)
template.pattern
elsif template.respond_to?(:to_str)
template.to_str
else
raise TypeError,
"Expected String, Addressable::URI, or Addressable::Template, " +
"got #{template.class}."
end
return Addressable::Template.new(@base_uri + template).expand(mapping)
end
##
# Returns on proc for special processing of retries for authorization errors
# Only 401s should be retried and only if the credentials are refreshable
#
# @param [#fetch_access_token!] authorization
# OAuth 2 credentials
# @return [Proc]
def authorization_error_handler(authorization)
can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token
Proc.new do |exception, tries|
next unless exception.kind_of?(AuthorizationError)
if can_refresh
begin
logger.debug("Attempting refresh of access token & retry of request")
authorization.fetch_access_token!
next
rescue Signet::AuthorizationError
end
end
raise exception
end
end
##
# Returns on proc for special processing of retries as not all client errors
# are recoverable. Only 401s should be retried (via authorization_error_handler)
#
# @return [Proc]
def client_error_handler
Proc.new do |exception, tries|
raise exception if exception.kind_of?(ClientError)
end
end
end
end

View File

@ -1,59 +0,0 @@
# Copyright 2013 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 'signet/oauth_2/client'
require_relative 'storage'
require_relative 'storages/file_store'
module Google
class APIClient
##
# Represents cached OAuth 2 tokens stored on local disk in a
# JSON serialized file. Meant to resemble the serialized format
# http://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.file.Storage-class.html
#
# @deprecated
# Use {Google::APIClient::Storage} and {Google::APIClient::FileStore} instead
#
class FileStorage
attr_accessor :storage
def initialize(path)
store = Google::APIClient::FileStore.new(path)
@storage = Google::APIClient::Storage.new(store)
@storage.authorize
end
def load_credentials
storage.authorize
end
def authorization
storage.authorization
end
##
# Write the credentials to the specified file.
#
# @param [Signet::OAuth2::Client] authorization
# Optional authorization instance. If not provided, the authorization
# already associated with this instance will be written.
def write_credentials(auth=nil)
storage.write_credentials(auth)
end
end
end
end

View File

@ -1,126 +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.
require 'jwt'
require 'signet/oauth_2/client'
require 'delegate'
module Google
class APIClient
##
# Generates access tokens using the JWT assertion profile. Requires a
# service account & access to the private key.
#
# @example Using Signet
#
# key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret')
# client.authorization = Signet::OAuth2::Client.new(
# :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
# :audience => 'https://accounts.google.com/o/oauth2/token',
# :scope => 'https://www.googleapis.com/auth/prediction',
# :issuer => '123456-abcdef@developer.gserviceaccount.com',
# :signing_key => key)
# client.authorization.fetch_access_token!
# client.execute(...)
#
# @deprecated
# Service accounts are now supported directly in Signet
# @see https://developers.google.com/accounts/docs/OAuth2ServiceAccount
class JWTAsserter
# @return [String] ID/email of the issuing party
attr_accessor :issuer
# @return [Fixnum] How long, in seconds, the assertion is valid for
attr_accessor :expiry
# @return [Fixnum] Seconds to expand the issued at/expiry window to account for clock skew
attr_accessor :skew
# @return [String] Scopes to authorize
attr_reader :scope
# @return [String,OpenSSL::PKey] key for signing assertions
attr_writer :key
# @return [String] Algorithm used for signing
attr_accessor :algorithm
##
# Initializes the asserter for a service account.
#
# @param [String] issuer
# Name/ID of the client issuing the assertion
# @param [String, Array] scope
# Scopes to authorize. May be a space delimited string or array of strings
# @param [String,OpenSSL::PKey] key
# Key for signing assertions
# @param [String] algorithm
# Algorithm to use, either 'RS256' for RSA with SHA-256
# or 'HS256' for HMAC with SHA-256
def initialize(issuer, scope, key, algorithm = "RS256")
self.issuer = issuer
self.scope = scope
self.expiry = 60 # 1 min default
self.skew = 60
self.key = key
self.algorithm = algorithm
end
##
# Set the scopes to authorize
#
# @param [String, Array] new_scope
# Scopes to authorize. May be a space delimited string or array of strings
def scope=(new_scope)
case new_scope
when Array
@scope = new_scope.join(' ')
when String
@scope = new_scope
when nil
@scope = ''
else
raise TypeError, "Expected Array or String, got #{new_scope.class}"
end
end
##
# Request a new access token.
#
# @param [String] person
# Email address of a user, if requesting a token to act on their behalf
# @param [Hash] options
# Pass through to Signet::OAuth2::Client.fetch_access_token
# @return [Signet::OAuth2::Client] Access token
#
# @see Signet::OAuth2::Client.fetch_access_token!
def authorize(person = nil, options={})
authorization = self.to_authorization(person)
authorization.fetch_access_token!(options)
return authorization
end
##
# Builds a Signet OAuth2 client
#
# @return [Signet::OAuth2::Client] Access token
def to_authorization(person = nil)
return Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => self.scope,
:issuer => @issuer,
:signing_key => @key,
:signing_algorithm => @algorithm,
:person => person
)
end
end
end
end

View File

@ -1,93 +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.
module Google
class APIClient
##
# Helper for loading keys from the PKCS12 files downloaded when
# setting up service accounts at the APIs Console.
#
module KeyUtils
##
# Loads a key from PKCS12 file, assuming a single private key
# is present.
#
# @param [String] keyfile
# Path of the PKCS12 file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
def self.load_from_pkcs12(keyfile, passphrase)
load_key(keyfile, passphrase) do |content, pass_phrase|
OpenSSL::PKCS12.new(content, pass_phrase).key
end
end
##
# Loads a key from a PEM file.
#
# @param [String] keyfile
# Path of the PEM file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
#
def self.load_from_pem(keyfile, passphrase)
load_key(keyfile, passphrase) do | content, pass_phrase|
OpenSSL::PKey::RSA.new(content, pass_phrase)
end
end
private
##
# Helper for loading keys from file or memory. Accepts a block
# to handle the specific file format.
#
# @param [String] keyfile
# Path of thefile to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @yield [String, String]
# Key file & passphrase to extract key from
# @yieldparam [String] keyfile
# Contents of the file
# @yieldparam [String] passphrase
# Passphrase to unlock key
# @yieldreturn [OpenSSL::PKey]
# Private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
def self.load_key(keyfile, passphrase, &block)
begin
begin
content = File.open(keyfile, 'rb') { |io| io.read }
rescue
content = keyfile
end
block.call(content, passphrase)
rescue OpenSSL::OpenSSLError
raise ArgumentError.new("Invalid keyfile or passphrase")
end
end
end
end
end

View File

@ -1,41 +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.
require 'google/api_client/auth/key_utils'
module Google
class APIClient
##
# Helper for loading keys from the PKCS12 files downloaded when
# setting up service accounts at the APIs Console.
#
module PKCS12
##
# Loads a key from PKCS12 file, assuming a single private key
# is present.
#
# @param [String] keyfile
# Path of the PKCS12 file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
# @deprecated
# Use {Google::APIClient::KeyUtils} instead
def self.load_key(keyfile, passphrase)
KeyUtils.load_from_pkcs12(keyfile, passphrase)
end
end
end
end

View File

@ -1,326 +0,0 @@
# Copyright 2012 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 'addressable/uri'
require 'google/api_client/reference'
require 'securerandom'
module Google
class APIClient
##
# Helper class to contain a response to an individual batched call.
#
# @api private
class BatchedCallResponse
# @return [String] UUID of the call
attr_reader :call_id
# @return [Fixnum] HTTP status code
attr_accessor :status
# @return [Hash] HTTP response headers
attr_accessor :headers
# @return [String] HTTP response body
attr_accessor :body
##
# Initialize the call response
#
# @param [String] call_id
# UUID of the original call
# @param [Fixnum] status
# HTTP status
# @param [Hash] headers
# HTTP response headers
# @param [#read, #to_str] body
# Response body
def initialize(call_id, status = nil, headers = nil, body = nil)
@call_id, @status, @headers, @body = call_id, status, headers, body
end
end
# Wraps multiple API calls into a single over-the-wire HTTP request.
#
# @example
#
# client = Google::APIClient.new
# urlshortener = client.discovered_api('urlshortener')
# batch = Google::APIClient::BatchRequest.new do |result|
# puts result.data
# end
#
# batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/foo' })
# batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/bar' })
#
# client.execute(batch)
#
class BatchRequest < Request
BATCH_BOUNDARY = "-----------RubyApiBatchRequest".freeze
# @api private
# @return [Array<(String,Google::APIClient::Request,Proc)] List of API calls in the batch
attr_reader :calls
##
# Creates a new batch request.
#
# @param [Hash] options
# Set of options for this request.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call defined
# a callback of its own.
#
# @return [Google::APIClient::BatchRequest]
# The constructed object.
#
# @yield [Google::APIClient::Result]
# block to be called when result ready
def initialize(options = {}, &block)
@calls = []
@global_callback = nil
@global_callback = block if block_given?
@last_auto_id = 0
@base_id = SecureRandom.uuid
options[:uri] ||= 'https://www.googleapis.com/batch'
options[:http_method] ||= 'POST'
super options
end
##
# Add a new call to the batch request.
# Each call must have its own call ID; if not provided, one will
# automatically be generated, avoiding collisions. If duplicate call IDs
# are provided, an error will be thrown.
#
# @param [Hash, Google::APIClient::Request] call
# the call to be added.
# @param [String] call_id
# the ID to be used for this call. Must be unique
# @param [Proc] block
# callback for this call's response.
#
# @return [Google::APIClient::BatchRequest]
# the BatchRequest, for chaining
#
# @yield [Google::APIClient::Result]
# block to be called when result ready
def add(call, call_id = nil, &block)
unless call.kind_of?(Google::APIClient::Reference)
call = Google::APIClient::Reference.new(call)
end
call_id ||= new_id
if @calls.assoc(call_id)
raise BatchError,
'A call with this ID already exists: %s' % call_id
end
callback = block_given? ? block : @global_callback
@calls << [call_id, call, callback]
return self
end
##
# Processes the HTTP response to the batch request, issuing callbacks.
#
# @api private
#
# @param [Faraday::Response] response
# the HTTP response.
def process_http_response(response)
content_type = find_header('Content-Type', response.headers)
m = /.*boundary=(.+)/.match(content_type)
if m
boundary = m[1]
parts = response.body.split(/--#{Regexp.escape(boundary)}/)
parts = parts[1...-1]
parts.each do |part|
call_response = deserialize_call_response(part)
_, call, callback = @calls.assoc(call_response.call_id)
result = Google::APIClient::Result.new(call, call_response)
callback.call(result) if callback
end
end
Google::APIClient::Result.new(self, response)
end
##
# Return the request body for the BatchRequest's HTTP request.
#
# @api private
#
# @return [String]
# the request body.
def to_http_request
if @calls.nil? || @calls.empty?
raise BatchError, 'Cannot make an empty batch request'
end
parts = @calls.map {|(call_id, call, _callback)| serialize_call(call_id, call)}
build_multipart(parts, 'multipart/mixed', BATCH_BOUNDARY)
super
end
protected
##
# Helper method to find a header from its name, regardless of case.
#
# @api private
#
# @param [String] name
# the name of the header to find.
# @param [Hash] headers
# the hash of headers and their values.
#
# @return [String]
# the value of the desired header.
def find_header(name, headers)
_, header = headers.detect do |h, v|
h.downcase == name.downcase
end
return header
end
##
# Create a new call ID. Uses an auto-incrementing, conflict-avoiding ID.
#
# @api private
#
# @return [String]
# the new, unique ID.
def new_id
@last_auto_id += 1
while @calls.assoc(@last_auto_id)
@last_auto_id += 1
end
return @last_auto_id.to_s
end
##
# Convert a Content-ID header value to an id. Presumes the Content-ID
# header conforms to the format that id_to_header() returns.
#
# @api private
#
# @param [String] header
# Content-ID header value.
#
# @return [String]
# The extracted ID value.
def header_to_id(header)
if !header.start_with?('<') || !header.end_with?('>') ||
!header.include?('+')
raise BatchError, 'Invalid value for Content-ID: "%s"' % header
end
_base, call_id = header[1...-1].split('+')
return Addressable::URI.unencode(call_id)
end
##
# Auxiliary method to split the headers from the body in an HTTP response.
#
# @api private
#
# @param [String] response
# the response to parse.
#
# @return [Array<Hash>, String]
# the headers and the body, separately.
def split_headers_and_body(response)
headers = {}
payload = response.lstrip
while payload
line, payload = payload.split("\n", 2)
line.sub!(/\s+\z/, '')
break if line.empty?
match = /\A([^:]+):\s*/.match(line)
if match
headers[match[1]] = match.post_match
else
raise BatchError, 'Invalid header line in response: %s' % line
end
end
return headers, payload
end
##
# Convert a single batched response into a BatchedCallResponse object.
#
# @api private
#
# @param [String] call_response
# the request to deserialize.
#
# @return [Google::APIClient::BatchedCallResponse]
# the parsed and converted response.
def deserialize_call_response(call_response)
outer_headers, outer_body = split_headers_and_body(call_response)
status_line, payload = outer_body.split("\n", 2)
_protocol, status, _reason = status_line.split(' ', 3)
headers, body = split_headers_and_body(payload)
content_id = find_header('Content-ID', outer_headers)
call_id = header_to_id(content_id)
return BatchedCallResponse.new(call_id, status.to_i, headers, body)
end
##
# Serialize a single batched call for assembling the multipart message
#
# @api private
#
# @param [Google::APIClient::Request] call
# the call to serialize.
#
# @return [Faraday::UploadIO]
# the serialized request
def serialize_call(call_id, call)
method, uri, headers, body = call.to_http_request
request = "#{method.to_s.upcase} #{Addressable::URI.parse(uri).request_uri} HTTP/1.1"
headers.each do |header, value|
request << "\r\n%s: %s" % [header, value]
end
if body
# TODO - CompositeIO if body is a stream
request << "\r\n\r\n"
if body.respond_to?(:read)
request << body.read
else
request << body.to_s
end
end
Faraday::UploadIO.new(StringIO.new(request), 'application/http', 'ruby-api-request', 'Content-ID' => id_to_header(call_id))
end
##
# Convert an id to a Content-ID header value.
#
# @api private
#
# @param [String] call_id
# identifier of individual call.
#
# @return [String]
# A Content-ID header with the call_id encoded into it. A UUID is
# prepended to the value because Content-ID headers are supposed to be
# universally unique.
def id_to_header(call_id)
return '<%s+%s>' % [@base_id, Addressable::URI.encode(call_id)]
end
end
end
end

View File

@ -1,33 +0,0 @@
require 'faraday'
require 'zlib'
module Google
class APIClient
class Charset < Faraday::Response::Middleware
include Google::APIClient::Logging
def charset_for_content_type(type)
if type
m = type.match(/(?:charset|encoding)="?([a-z0-9-]+)"?/i)
if m
return Encoding.find(m[1])
end
end
nil
end
def adjust_encoding(env)
charset = charset_for_content_type(env[:response_headers]['content-type'])
if charset && env[:body].encoding != charset
env[:body].force_encoding(charset)
end
end
def on_complete(env)
adjust_encoding(env)
end
end
end
end
Faraday::Response.register_middleware :charset => Google::APIClient::Charset

View File

@ -1,310 +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.
require 'addressable/uri'
require 'multi_json'
require 'active_support/inflector'
require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method'
require 'google/api_client/discovery/media'
module Google
class APIClient
##
# A service that has been described by a discovery document.
class API
##
# Creates a description of a particular version of a service.
#
# @param [String] document_base
# Base URI for the discovery document.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this service
# version.
#
# @return [Google::APIClient::API] The constructed service object.
def initialize(document_base, discovery_document)
@document_base = Addressable::URI.parse(document_base)
@discovery_document = discovery_document
metaclass = (class << self; self; end)
self.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { resource }
end
end
self.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { method }
end
end
end
# @return [String] unparsed discovery document for the API
attr_reader :discovery_document
##
# Returns the id of the service.
#
# @return [String] The service id.
def id
return (
@discovery_document['id'] ||
"#{self.name}:#{self.version}"
)
end
##
# Returns the identifier for the service.
#
# @return [String] The service identifier.
def name
return @discovery_document['name']
end
##
# Returns the version of the service.
#
# @return [String] The service version.
def version
return @discovery_document['version']
end
##
# Returns a human-readable title for the API.
#
# @return [Hash] The API title.
def title
return @discovery_document['title']
end
##
# Returns a human-readable description of the API.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Returns a URI for the API documentation.
#
# @return [Hash] The API documentation.
def documentation
return Addressable::URI.parse(@discovery_document['documentationLink'])
end
##
# Returns true if this is the preferred version of this API.
#
# @return [TrueClass, FalseClass]
# Whether or not this is the preferred version of this API.
def preferred
return !!@discovery_document['preferred']
end
##
# Returns the list of API features.
#
# @return [Array]
# The features supported by this API.
def features
return @discovery_document['features'] || []
end
##
# Returns the root URI for this service.
#
# @return [Addressable::URI] The root URI.
def root_uri
return @root_uri ||= (
Addressable::URI.parse(self.discovery_document['rootUrl'])
)
end
##
# Returns true if this API uses a data wrapper.
#
# @return [TrueClass, FalseClass]
# Whether or not this API uses a data wrapper.
def data_wrapper?
return self.features.include?('dataWrapper')
end
##
# Returns the base URI for the discovery document.
#
# @return [Addressable::URI] The base URI.
attr_reader :document_base
##
# Returns the base URI for this version of the service.
#
# @return [Addressable::URI] The base URI that methods are joined to.
def method_base
if @discovery_document['basePath']
return @method_base ||= (
self.root_uri.join(Addressable::URI.parse(@discovery_document['basePath']))
).normalize
else
return nil
end
end
##
# Updates the hierarchy of resources and methods with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the service.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
self.discovered_resources.each do |resource|
resource.method_base = @method_base
end
self.discovered_methods.each do |method|
method.method_base = @method_base
end
end
##
# Returns the base URI for batch calls to this service.
#
# @return [Addressable::URI] The base URI that methods are joined to.
def batch_path
if @discovery_document['batchPath']
return @batch_path ||= (
self.document_base.join(Addressable::URI.parse('/' +
@discovery_document['batchPath']))
).normalize
else
return nil
end
end
##
# A list of schemas available for this version of the API.
#
# @return [Hash] A list of {Google::APIClient::Schema} objects.
def schemas
return @schemas ||= (
(@discovery_document['schemas'] || []).inject({}) do |accu, (k, v)|
accu[k] = Google::APIClient::Schema.parse(self, v)
accu
end
)
end
##
# Returns a schema for a kind value.
#
# @return [Google::APIClient::Schema] The associated Schema object.
def schema_for_kind(kind)
api_name, schema_name = kind.split('#', 2)
if api_name != self.name
raise ArgumentError,
"The kind does not match this API. " +
"Expected '#{self.name}', got '#{api_name}'."
end
for k, v in self.schemas
return v if k.downcase == schema_name.downcase
end
return nil
end
##
# A list of resources available at the root level of this version of the
# API.
#
# @return [Array] A list of {Google::APIClient::Resource} objects.
def discovered_resources
return @discovered_resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(
self, self.method_base, k, v
)
accu
end
)
end
##
# A list of methods available at the root level of this version of the
# API.
#
# @return [Array] A list of {Google::APIClient::Method} objects.
def discovered_methods
return @discovered_methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(self, self.method_base, k, v)
accu
end
)
end
##
# Allows deep inspection of the discovery document.
def [](key)
return @discovery_document[key]
end
##
# Converts the service to a flat mapping of RPC names and method objects.
#
# @return [Hash] All methods available on the service.
#
# @example
# # Discover available methods
# method_names = client.discovered_api('buzz').to_h.keys
def to_h
return @hash ||= (begin
methods_hash = {}
self.discovered_methods.each do |method|
methods_hash[method.id] = method
end
self.discovered_resources.each do |resource|
methods_hash.merge!(resource.to_h)
end
methods_hash
end)
end
##
# Returns a <code>String</code> representation of the service's state.
#
# @return [String] The service's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x ID:%s>", self.class.to_s, self.object_id, self.id
)
end
##
# Marshalling support - serialize the API to a string (doc base + original
# discovery document).
def _dump(level)
MultiJson.dump([@document_base.to_s, @discovery_document])
end
##
# Marshalling support - Restore an API instance from serialized form
def self._load(obj)
new(*MultiJson.load(obj))
end
end
end
end

View File

@ -1,77 +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.
require 'addressable/uri'
require 'addressable/template'
require 'google/api_client/errors'
module Google
class APIClient
##
# Media upload elements for discovered methods
class MediaUpload
##
# Creates a description of a particular method.
#
# @param [Google::APIClient::API] api
# Base discovery document for the API
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [Hash] discovery_document
# The media upload section of the discovery document.
#
# @return [Google::APIClient::Method] The constructed method object.
def initialize(api, method_base, discovery_document)
@api = api
@method_base = method_base
@discovery_document = discovery_document
end
##
# List of acceptable mime types
#
# @return [Array]
# List of acceptable mime types for uploaded content
def accepted_types
@discovery_document['accept']
end
##
# Maximum size of an uplad
# TODO: Parse & convert to numeric value
#
# @return [String]
def max_size
@discovery_document['maxSize']
end
##
# Returns the URI template for the method. A parameter list can be
# used to expand this into a URI.
#
# @return [Addressable::Template] The URI template.
def uri_template
return @uri_template ||= Addressable::Template.new(
@api.method_base.join(Addressable::URI.parse(@discovery_document['protocols']['simple']['path']))
)
end
end
end
end

View File

@ -1,363 +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.
require 'addressable/uri'
require 'addressable/template'
require 'google/api_client/errors'
module Google
class APIClient
##
# A method that has been described by a discovery document.
class Method
##
# Creates a description of a particular method.
#
# @param [Google::APIClient::API] api
# The API this method belongs to.
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [String] method_name
# The identifier for the method.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this method.
#
# @return [Google::APIClient::Method] The constructed method object.
def initialize(api, method_base, method_name, discovery_document)
@api = api
@method_base = method_base
@name = method_name
@discovery_document = discovery_document
end
# @return [String] unparsed discovery document for the method
attr_reader :discovery_document
##
# Returns the API this method belongs to.
#
# @return [Google::APIClient::API] The API this method belongs to.
attr_reader :api
##
# Returns the identifier for the method.
#
# @return [String] The method identifier.
attr_reader :name
##
# Returns the base URI for the method.
#
# @return [Addressable::URI]
# The base URI that this method will be joined to.
attr_reader :method_base
##
# Updates the method with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the method.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
@uri_template = nil
end
##
# Returns a human-readable description of the method.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Returns the method ID.
#
# @return [String] The method identifier.
def id
return @discovery_document['id']
end
##
# Returns the HTTP method or 'GET' if none is specified.
#
# @return [String] The HTTP method that will be used in the request.
def http_method
return @discovery_document['httpMethod'] || 'GET'
end
##
# Returns the URI template for the method. A parameter list can be
# used to expand this into a URI.
#
# @return [Addressable::Template] The URI template.
def uri_template
return @uri_template ||= Addressable::Template.new(
self.method_base.join(Addressable::URI.parse("./" + @discovery_document['path']))
)
end
##
# Returns media upload information for this method, if supported
#
# @return [Google::APIClient::MediaUpload] Description of upload endpoints
def media_upload
if @discovery_document['mediaUpload']
return @media_upload ||= Google::APIClient::MediaUpload.new(self, self.method_base, @discovery_document['mediaUpload'])
else
return nil
end
end
##
# Returns the Schema object for the method's request, if any.
#
# @return [Google::APIClient::Schema] The request schema.
def request_schema
if @discovery_document['request']
schema_name = @discovery_document['request']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
##
# Returns the Schema object for the method's response, if any.
#
# @return [Google::APIClient::Schema] The response schema.
def response_schema
if @discovery_document['response']
schema_name = @discovery_document['response']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
##
# Normalizes parameters, converting to the appropriate types.
#
# @param [Hash, Array] parameters
# The parameters to normalize.
#
# @return [Hash] The normalized parameters.
def normalize_parameters(parameters={})
# Convert keys to Strings when appropriate
if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
# Returning an array since parameters can be repeated (ie, Adsense Management API)
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
else
raise TypeError,
"Expected Hash or Array, got #{parameters.class}."
end
return parameters
end
##
# Expands the method's URI template using a parameter list.
#
# @api private
# @param [Hash, Array] parameters
# The parameter list to use.
#
# @return [Addressable::URI] The URI after expansion.
def generate_uri(parameters={})
parameters = self.normalize_parameters(parameters)
self.validate_parameters(parameters)
template_variables = self.uri_template.variables
upload_type = parameters.assoc('uploadType') || parameters.assoc('upload_type')
if upload_type
unless self.media_upload
raise ArgumentException, "Media upload not supported for this method"
end
case upload_type.last
when 'media', 'multipart', 'resumable'
uri = self.media_upload.uri_template.expand(parameters)
else
raise ArgumentException, "Invalid uploadType '#{upload_type}'"
end
else
uri = self.uri_template.expand(parameters)
end
query_parameters = parameters.reject do |k, v|
template_variables.include?(k)
end
# encode all non-template parameters
params = ""
unless query_parameters.empty?
params = "?" + Addressable::URI.form_encode(query_parameters.sort)
end
# Normalization is necessary because of undesirable percent-escaping
# during URI template expansion
return uri.normalize + params
end
##
# Generates an HTTP request for this method.
#
# @api private
# @param [Hash, Array] parameters
# The parameters to send.
# @param [String, StringIO] body The body for the HTTP request.
# @param [Hash, Array] headers The HTTP headers for the request.
# @option options [Faraday::Connection] :connection
# The HTTP connection to use.
#
# @return [Array] The generated HTTP request.
def generate_request(parameters={}, body='', headers={}, options={})
if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
raise TypeError, "Expected Hash or Array, got #{headers.class}."
end
method = self.http_method.to_s.downcase.to_sym
uri = self.generate_uri(parameters)
headers = Faraday::Utils::Headers.new(headers)
return [method, uri, headers, body]
end
##
# Returns a <code>Hash</code> of the parameter descriptions for
# this method.
#
# @return [Hash] The parameter descriptions.
def parameter_descriptions
@parameter_descriptions ||= (
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }
end
##
# Returns an <code>Array</code> of the parameters for this method.
#
# @return [Array] The parameters.
def parameters
@parameters ||= ((
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Returns an <code>Array</code> of the required parameters for this
# method.
#
# @return [Array] The required parameters.
#
# @example
# # A list of all required parameters.
# method.required_parameters
def required_parameters
@required_parameters ||= ((self.parameter_descriptions.select do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Returns an <code>Array</code> of the optional parameters for this
# method.
#
# @return [Array] The optional parameters.
#
# @example
# # A list of all optional parameters.
# method.optional_parameters
def optional_parameters
@optional_parameters ||= ((self.parameter_descriptions.reject do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Verifies that the parameters are valid for this method. Raises an
# exception if validation fails.
#
# @api private
# @param [Hash, Array] parameters
# The parameters to verify.
#
# @return [NilClass] <code>nil</code> if validation passes.
def validate_parameters(parameters={})
parameters = self.normalize_parameters(parameters)
required_variables = ((self.parameter_descriptions.select do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
missing_variables = required_variables - parameters.map { |(k, _)| k }
if missing_variables.size > 0
raise ArgumentError,
"Missing required parameters: #{missing_variables.join(', ')}."
end
parameters.each do |k, v|
# Handle repeated parameters.
if self.parameter_descriptions[k] &&
self.parameter_descriptions[k]['repeated'] &&
v.kind_of?(Array)
# If this is a repeated parameter and we've got an array as a
# value, just provide the whole array to the loop below.
items = v
else
# If this is not a repeated parameter, or if it is but we're
# being given a single value, wrap the value in an array, so that
# the loop below still works for the single element.
items = [v]
end
items.each do |item|
if self.parameter_descriptions[k]
enum = self.parameter_descriptions[k]['enum']
if enum && !enum.include?(item)
raise ArgumentError,
"Parameter '#{k}' has an invalid value: #{item}. " +
"Must be one of #{enum.inspect}."
end
pattern = self.parameter_descriptions[k]['pattern']
if pattern
regexp = Regexp.new("^#{pattern}$")
if item !~ regexp
raise ArgumentError,
"Parameter '#{k}' has an invalid value: #{item}. " +
"Must match: /^#{pattern}$/."
end
end
end
end
end
return nil
end
##
# Returns a <code>String</code> representation of the method's state.
#
# @return [String] The method's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x ID:%s>",
self.class.to_s, self.object_id, self.id
)
end
end
end
end

View File

@ -1,156 +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.
require 'addressable/uri'
require 'active_support/inflector'
require 'google/api_client/discovery/method'
module Google
class APIClient
##
# A resource that has been described by a discovery document.
class Resource
##
# Creates a description of a particular version of a resource.
#
# @param [Google::APIClient::API] api
# The API this resource belongs to.
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [String] resource_name
# The identifier for the resource.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this resource.
#
# @return [Google::APIClient::Resource] The constructed resource object.
def initialize(api, method_base, resource_name, discovery_document)
@api = api
@method_base = method_base
@name = resource_name
@discovery_document = discovery_document
metaclass = (class <<self; self; end)
self.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { resource }
end
end
self.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { method }
end
end
end
# @return [String] unparsed discovery document for the resource
attr_reader :discovery_document
##
# Returns the identifier for the resource.
#
# @return [String] The resource identifier.
attr_reader :name
##
# Returns the base URI for this resource.
#
# @return [Addressable::URI] The base URI that methods are joined to.
attr_reader :method_base
##
# Returns a human-readable description of the resource.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Updates the hierarchy of resources and methods with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the resource.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
self.discovered_resources.each do |resource|
resource.method_base = @method_base
end
self.discovered_methods.each do |method|
method.method_base = @method_base
end
end
##
# A list of sub-resources available on this resource.
#
# @return [Array] A list of {Google::APIClient::Resource} objects.
def discovered_resources
return @discovered_resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(
@api, self.method_base, k, v
)
accu
end
)
end
##
# A list of methods available on this resource.
#
# @return [Array] A list of {Google::APIClient::Method} objects.
def discovered_methods
return @discovered_methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(@api, self.method_base, k, v)
accu
end
)
end
##
# Converts the resource to a flat mapping of RPC names and method
# objects.
#
# @return [Hash] All methods available on the resource.
def to_h
return @hash ||= (begin
methods_hash = {}
self.discovered_methods.each do |method|
methods_hash[method.id] = method
end
self.discovered_resources.each do |resource|
methods_hash.merge!(resource.to_h)
end
methods_hash
end)
end
##
# Returns a <code>String</code> representation of the resource's state.
#
# @return [String] The resource's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
)
end
end
end
end

View File

@ -1,117 +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.
require 'time'
require 'multi_json'
require 'compat/multi_json'
require 'base64'
require 'autoparse'
require 'addressable/uri'
require 'addressable/template'
require 'active_support/inflector'
require 'google/api_client/errors'
module Google
class APIClient
##
# @api private
module Schema
def self.parse(api, schema_data)
# This method is super-long, but hard to break up due to the
# unavoidable dependence on closures and execution context.
schema_name = schema_data['id']
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this code once this has been resolved.
schema_uri = (
api.document_base +
(schema_name[0..0] != '#' ? '#' + schema_name : schema_name)
)
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this whole lambda once this has been resolved.
reformat_references = lambda do |data|
# This code is not particularly efficient due to recursive traversal
# and excess object creation, but this hopefully shouldn't be an
# issue since it should only be called only once per schema per
# process.
if data.kind_of?(Hash) &&
data['$ref'] && !data['$ref'].kind_of?(Hash)
if data['$ref'].respond_to?(:to_str)
reference = data['$ref'].to_str
else
raise TypeError, "Expected String, got #{data['$ref'].class}"
end
reference = '#' + reference if reference[0..0] != '#'
data.merge({
'$ref' => reference
})
elsif data.kind_of?(Hash)
data.inject({}) do |accu, (key, value)|
if value.kind_of?(Hash)
accu[key] = reformat_references.call(value)
else
accu[key] = value
end
accu
end
else
data
end
end
schema_data = reformat_references.call(schema_data)
if schema_name
api_name_string = ActiveSupport::Inflector.camelize(api.name)
api_version_string = ActiveSupport::Inflector.camelize(api.version).gsub('.', '_')
# This is for compatibility with Ruby 1.8.7.
# TODO(bobaman) Remove this when we eventually stop supporting 1.8.7.
args = []
args << false if Class.method(:const_defined?).arity != 1
if Google::APIClient::Schema.const_defined?(api_name_string, *args)
api_name = Google::APIClient::Schema.const_get(
api_name_string, *args
)
else
api_name = Google::APIClient::Schema.const_set(
api_name_string, Module.new
)
end
if api_name.const_defined?(api_version_string, *args)
api_version = api_name.const_get(api_version_string, *args)
else
api_version = api_name.const_set(api_version_string, Module.new)
end
if api_version.const_defined?(schema_name, *args)
schema_class = api_version.const_get(schema_name, *args)
end
end
# It's possible the schema has already been defined. If so, don't
# redefine it. This means that reloading a schema which has already
# been loaded into memory is not possible.
unless schema_class
schema_class = AutoParse.generate(schema_data, :uri => schema_uri)
if schema_name
api_version.const_set(schema_name, schema_class)
end
end
return schema_class
end
end
end
end

View File

@ -1,42 +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.
module Google
class APIClient
module ENV
OS_VERSION = begin
if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
# TODO(bobaman)
# Confirm that all of these Windows environments actually have access
# to the `ver` command.
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
elsif RUBY_PLATFORM =~ /darwin/i
"Mac OS X/#{`sw_vers -productVersion`}"
elsif RUBY_PLATFORM == 'java'
# Get the information from java system properties to avoid spawning a
# sub-process, which is not friendly in some contexts (web servers).
require 'java'
name = java.lang.System.getProperty('os.name')
version = java.lang.System.getProperty('os.version')
"#{name}/#{version}"
else
`uname -sr`.sub(' ', '/')
end
rescue Exception
RUBY_PLATFORM
end
end
end
end

View File

@ -1,28 +0,0 @@
require 'faraday'
require 'zlib'
module Google
class APIClient
class Gzip < Faraday::Response::Middleware
include Google::APIClient::Logging
def on_complete(env)
encoding = env[:response_headers]['content-encoding'].to_s.downcase
case encoding
when 'gzip'
logger.debug { "Decompressing gzip encoded response (#{env[:body].length} bytes)" }
env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body])).read
env[:response_headers].delete('content-encoding')
logger.debug { "Decompressed (#{env[:body].length} bytes)" }
when 'deflate'
logger.debug{ "Decompressing deflate encoded response (#{env[:body].length} bytes)" }
env[:body] = Zlib::Inflate.inflate(env[:body])
env[:response_headers].delete('content-encoding')
logger.debug { "Decompressed (#{env[:body].length} bytes)" }
end
end
end
end
end
Faraday::Response.register_middleware :gzip => Google::APIClient::Gzip

View File

@ -1,32 +0,0 @@
require 'logger'
module Google
class APIClient
class << self
##
# Logger for the API client
#
# @return [Logger] logger instance.
attr_accessor :logger
end
self.logger = Logger.new(STDOUT)
self.logger.level = Logger::WARN
##
# Module to make accessing the logger simpler
module Logging
##
# Logger for the API client
#
# @return [Logger] logger instance.
def logger
Google::APIClient.logger
end
end
end
end

View File

@ -1,259 +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.
require 'google/api_client/reference'
module Google
class APIClient
##
# Uploadable media support. Holds an IO stream & content type.
#
# @see Faraday::UploadIO
# @example
# media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
class UploadIO < Faraday::UploadIO
# @return [Fixnum] Size of chunks to upload. Default is nil, meaning upload the entire file in a single request
attr_accessor :chunk_size
##
# Get the length of the stream
#
# @return [Fixnum]
# Length of stream, in bytes
def length
io.respond_to?(:length) ? io.length : File.size(local_path)
end
end
##
# Wraps an input stream and limits data to a given range
#
# @example
# chunk = Google::APIClient::RangedIO.new(io, 0, 1000)
class RangedIO
##
# Bind an input stream to a specific range.
#
# @param [IO] io
# Source input stream
# @param [Fixnum] offset
# Starting offset of the range
# @param [Fixnum] length
# Length of range
def initialize(io, offset, length)
@io = io
@offset = offset
@length = length
self.rewind
end
##
# @see IO#read
def read(amount = nil, buf = nil)
buffer = buf || ''
if amount.nil?
size = @length - @pos
done = ''
elsif amount == 0
size = 0
done = ''
else
size = [@length - @pos, amount].min
done = nil
end
if size > 0
result = @io.read(size)
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
buffer << result if result
@pos = @pos + size
end
if buffer.length > 0
buffer
else
done
end
end
##
# @see IO#rewind
def rewind
self.pos = 0
end
##
# @see IO#pos
def pos
@pos
end
##
# @see IO#pos=
def pos=(pos)
@pos = pos
@io.pos = @offset + pos
end
end
##
# Resumable uploader.
#
class ResumableUpload < Request
# @return [Fixnum] Max bytes to send in a single request
attr_accessor :chunk_size
##
# Creates a new uploader.
#
# @param [Hash] options
# Request options
def initialize(options={})
super options
self.uri = options[:uri]
self.http_method = :put
@offset = options[:offset] || 0
@complete = false
@expired = false
end
##
# Sends all remaining chunks to the server
#
# @deprecated Pass the instance to {Google::APIClient#execute} instead
#
# @param [Google::APIClient] api_client
# API Client instance to use for sending
def send_all(api_client)
result = nil
until complete?
result = send_chunk(api_client)
break unless result.status == 308
end
return result
end
##
# Sends the next chunk to the server
#
# @deprecated Pass the instance to {Google::APIClient#execute} instead
#
# @param [Google::APIClient] api_client
# API Client instance to use for sending
def send_chunk(api_client)
return api_client.execute(self)
end
##
# Check if upload is complete
#
# @return [TrueClass, FalseClass]
# Whether or not the upload complete successfully
def complete?
return @complete
end
##
# Check if the upload URL expired (upload not completed in alotted time.)
# Expired uploads must be restarted from the beginning
#
# @return [TrueClass, FalseClass]
# Whether or not the upload has expired and can not be resumed
def expired?
return @expired
end
##
# Check if upload is resumable. That is, neither complete nor expired
#
# @return [TrueClass, FalseClass] True if upload can be resumed
def resumable?
return !(self.complete? or self.expired?)
end
##
# Convert to an HTTP request. Returns components in order of method, URI,
# request headers, and body
#
# @api private
#
# @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
def to_http_request
if @complete
raise Google::APIClient::ClientError, "Upload already complete"
elsif @offset.nil?
self.headers.update({
'Content-Length' => "0",
'Content-Range' => "bytes */#{media.length}" })
else
start_offset = @offset
remaining = self.media.length - start_offset
chunk_size = self.media.chunk_size || self.chunk_size || self.media.length
content_length = [remaining, chunk_size].min
chunk = RangedIO.new(self.media.io, start_offset, content_length)
end_offset = start_offset + content_length - 1
self.headers.update({
'Content-Length' => "#{content_length}",
'Content-Type' => self.media.content_type,
'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" })
self.body = chunk
end
super
end
##
# Check the result from the server, updating the offset and/or location
# if available.
#
# @api private
#
# @param [Faraday::Response] response
# HTTP response
#
# @return [Google::APIClient::Result]
# Processed API response
def process_http_response(response)
case response.status
when 200...299
@complete = true
when 308
range = response.headers['range']
if range
@offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
end
if response.headers['location']
self.uri = response.headers['location']
end
when 400...499
@expired = true
when 500...599
# Invalidate the offset to mark it needs to be queried on the
# next request
@offset = nil
end
return Google::APIClient::Result.new(self, response)
end
##
# Hashified verison of the API request
#
# @return [Hash]
def to_hash
super.merge(:offset => @offset)
end
end
end
end

View File

@ -1,18 +0,0 @@
require 'rails/railtie'
require 'google/api_client/logging'
module Google
class APIClient
##
# Optional support class for Rails. Currently replaces the built-in logger
# with Rails' application log.
#
class Railtie < Rails::Railtie
initializer 'google-api-client' do |app|
logger = app.config.logger || Rails.logger
Google::APIClient.logger = logger unless logger.nil?
end
end
end
end

View File

@ -1,350 +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.
require 'faraday'
require 'faraday/request/multipart'
require 'compat/multi_json'
require 'addressable/uri'
require 'stringio'
require 'google/api_client/discovery'
require 'google/api_client/logging'
module Google
class APIClient
##
# Represents an API request.
class Request
include Google::APIClient::Logging
MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
# @return [Hash] Request parameters
attr_reader :parameters
# @return [Hash] Additional HTTP headers
attr_reader :headers
# @return [Google::APIClient::Method] API method to invoke
attr_reader :api_method
# @return [Google::APIClient::UploadIO] File to upload
attr_accessor :media
# @return [#generated_authenticated_request] User credentials
attr_accessor :authorization
# @return [TrueClass,FalseClass] True if request should include credentials
attr_accessor :authenticated
# @return [#read, #to_str] Request body
attr_accessor :body
##
# Build a request
#
# @param [Hash] options
# @option options [Hash, Array] :parameters
# Request parameters for the API method.
# @option options [Google::APIClient::Method] :api_method
# API method to invoke. Either :api_method or :uri must be specified
# @option options [TrueClass, FalseClass] :authenticated
# True if request should include credentials. Implicitly true if
# unspecified and :authorization present
# @option options [#generate_signed_request] :authorization
# OAuth credentials
# @option options [Google::APIClient::UploadIO] :media
# File to upload, if media upload request
# @option options [#to_json, #to_hash] :body_object
# Main body of the API request. Typically hash or object that can
# be serialized to JSON
# @option options [#read, #to_str] :body
# Raw body to send in POST/PUT requests
# @option options [String, Addressable::URI] :uri
# URI to request. Either :api_method or :uri must be specified
# @option options [String, Symbol] :http_method
# HTTP method when requesting a URI
def initialize(options={})
@parameters = Faraday::Utils::ParamsHash.new
@headers = Faraday::Utils::Headers.new
self.parameters.merge!(options[:parameters]) unless options[:parameters].nil?
self.headers.merge!(options[:headers]) unless options[:headers].nil?
self.api_method = options[:api_method]
self.authenticated = options[:authenticated]
self.authorization = options[:authorization]
# These parameters are handled differently because they're not
# parameters to the API method, but rather to the API system.
self.parameters['key'] ||= options[:key] if options[:key]
self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
if options[:media]
self.initialize_media_upload(options)
elsif options[:body]
self.body = options[:body]
elsif options[:body_object]
self.headers['Content-Type'] ||= 'application/json'
self.body = serialize_body(options[:body_object])
else
self.body = ''
end
unless self.api_method
self.http_method = options[:http_method] || 'GET'
self.uri = options[:uri]
end
end
# @!attribute [r] upload_type
# @return [String] protocol used for upload
def upload_type
return self.parameters['uploadType'] || self.parameters['upload_type']
end
# @!attribute http_method
# @return [Symbol] HTTP method if invoking a URI
def http_method
return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
end
def http_method=(new_http_method)
if new_http_method.kind_of?(Symbol)
@http_method = new_http_method.to_s.downcase.to_sym
elsif new_http_method.respond_to?(:to_str)
@http_method = new_http_method.to_s.downcase.to_sym
else
raise TypeError,
"Expected String or Symbol, got #{new_http_method.class}."
end
end
def api_method=(new_api_method)
if new_api_method.nil? || new_api_method.kind_of?(Google::APIClient::Method)
@api_method = new_api_method
else
raise TypeError,
"Expected Google::APIClient::Method, got #{new_api_method.class}."
end
end
# @!attribute uri
# @return [Addressable::URI] URI to send request
def uri
return @uri ||= self.api_method.generate_uri(self.parameters)
end
def uri=(new_uri)
@uri = Addressable::URI.parse(new_uri)
@parameters.update(@uri.query_values) unless @uri.query_values.nil?
end
# Transmits the request with the given connection
#
# @api private
#
# @param [Faraday::Connection] connection
# the connection to transmit with
# @param [TrueValue,FalseValue] is_retry
# True if request has been previous sent
#
# @return [Google::APIClient::Result]
# result of API request
def send(connection, is_retry = false)
self.body.rewind if is_retry && self.body.respond_to?(:rewind)
env = self.to_env(connection)
logger.debug { "#{self.class} Sending API request #{env[:method]} #{env[:url].to_s} #{env[:request_headers]}" }
http_response = connection.app.call(env)
result = self.process_http_response(http_response)
logger.debug { "#{self.class} Result: #{result.status} #{result.headers}" }
# Resumamble slightly different than other upload protocols in that it requires at least
# 2 requests.
if result.status == 200 && self.upload_type == 'resumable' && self.media
upload = result.resumable_upload
unless upload.complete?
logger.debug { "#{self.class} Sending upload body" }
result = upload.send(connection)
end
end
return result
end
# Convert to an HTTP request. Returns components in order of method, URI,
# request headers, and body
#
# @api private
#
# @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
def to_http_request
request = (
if self.api_method
self.api_method.generate_request(self.parameters, self.body, self.headers)
elsif self.uri
unless self.parameters.empty?
self.uri.query = Addressable::URI.form_encode(self.parameters)
end
[self.http_method, self.uri.to_s, self.headers, self.body]
end)
return request
end
##
# Hashified verison of the API request
#
# @return [Hash]
def to_hash
options = {}
if self.api_method
options[:api_method] = self.api_method
options[:parameters] = self.parameters
else
options[:http_method] = self.http_method
options[:uri] = self.uri
end
options[:headers] = self.headers
options[:body] = self.body
options[:media] = self.media
unless self.authorization.nil?
options[:authorization] = self.authorization
end
return options
end
##
# Prepares the request for execution, building a hash of parts
# suitable for sending to Faraday::Connection.
#
# @api private
#
# @param [Faraday::Connection] connection
# Connection for building the request
#
# @return [Hash]
# Encoded request
def to_env(connection)
method, uri, headers, body = self.to_http_request
http_request = connection.build_request(method) do |req|
req.url(uri.to_s)
req.headers.update(headers)
req.body = body
end
if self.authorization.respond_to?(:generate_authenticated_request)
http_request = self.authorization.generate_authenticated_request(
:request => http_request,
:connection => connection
)
end
http_request.to_env(connection)
end
##
# Convert HTTP response to an API Result
#
# @api private
#
# @param [Faraday::Response] response
# HTTP response
#
# @return [Google::APIClient::Result]
# Processed API response
def process_http_response(response)
Result.new(self, response)
end
protected
##
# Adjust headers & body for media uploads
#
# @api private
#
# @param [Hash] options
# @option options [Hash, Array] :parameters
# Request parameters for the API method.
# @option options [Google::APIClient::UploadIO] :media
# File to upload, if media upload request
# @option options [#to_json, #to_hash] :body_object
# Main body of the API request. Typically hash or object that can
# be serialized to JSON
# @option options [#read, #to_str] :body
# Raw body to send in POST/PUT requests
def initialize_media_upload(options)
self.media = options[:media]
case self.upload_type
when "media"
if options[:body] || options[:body_object]
raise ArgumentError, "Can not specify body & body object for simple uploads"
end
self.headers['Content-Type'] ||= self.media.content_type
self.headers['Content-Length'] ||= self.media.length.to_s
self.body = self.media
when "multipart"
unless options[:body_object]
raise ArgumentError, "Multipart requested but no body object"
end
metadata = StringIO.new(serialize_body(options[:body_object]))
build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
when "resumable"
file_length = self.media.length
self.headers['X-Upload-Content-Type'] = self.media.content_type
self.headers['X-Upload-Content-Length'] = file_length.to_s
if options[:body_object]
self.headers['Content-Type'] ||= 'application/json'
self.body = serialize_body(options[:body_object])
else
self.body = ''
end
end
end
##
# Assemble a multipart message from a set of parts
#
# @api private
#
# @param [Array<[#read,#to_str]>] parts
# Array of parts to encode.
# @param [String] mime_type
# MIME type of the message
# @param [String] boundary
# Boundary for separating each part of the message
def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY)
env = Faraday::Env.new
env.request = Faraday::RequestOptions.new
env.request.boundary = boundary
env.request_headers = {'Content-Type' => "#{mime_type};boundary=#{boundary}"}
multipart = Faraday::Request::Multipart.new
self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
self.headers.update(env[:request_headers])
end
##
# Serialize body object to JSON
#
# @api private
#
# @param [#to_json,#to_hash] body
# object to serialize
#
# @return [String]
# JSON
def serialize_body(body)
return body.to_json if body.respond_to?(:to_json)
return MultiJson.dump(body.to_hash) if body.respond_to?(:to_hash)
raise TypeError, 'Could not convert body object to JSON.' +
'Must respond to :to_json or :to_hash.'
end
end
end
end

View File

@ -1,259 +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.
module Google
class APIClient
##
# This class wraps a result returned by an API call.
class Result
extend Forwardable
##
# Init the result
#
# @param [Google::APIClient::Request] request
# The original request
# @param [Faraday::Response] response
# Raw HTTP Response
def initialize(request, response)
@request = request
@response = response
@media_upload = reference if reference.kind_of?(ResumableUpload)
end
# @return [Google::APIClient::Request] Original request object
attr_reader :request
# @return [Faraday::Response] HTTP response
attr_reader :response
# @!attribute [r] reference
# @return [Google::APIClient::Request] Original request object
# @deprecated See {#request}
alias_method :reference, :request # For compatibility with pre-beta clients
# @!attribute [r] status
# @return [Fixnum] HTTP status code
# @!attribute [r] headers
# @return [Hash] HTTP response headers
# @!attribute [r] body
# @return [String] HTTP response body
def_delegators :@response, :status, :headers, :body
# @!attribute [r] resumable_upload
# @return [Google::APIClient::ResumableUpload] For resuming media uploads
def resumable_upload
@media_upload ||= (
options = self.reference.to_hash.merge(
:uri => self.headers['location'],
:media => self.reference.media
)
Google::APIClient::ResumableUpload.new(options)
)
end
##
# Get the content type of the response
# @!attribute [r] media_type
# @return [String]
# Value of content-type header
def media_type
_, content_type = self.headers.detect do |h, v|
h.downcase == 'Content-Type'.downcase
end
if content_type
return content_type[/^([^;]*);?.*$/, 1].strip.downcase
else
return nil
end
end
##
# Check if request failed - which is anything other than 200/201 OK
#
# @!attribute [r] error?
# @return [TrueClass, FalseClass]
# true if result of operation is an error
def error?
return !self.success?
end
##
# Check if request was successful
#
# @!attribute [r] success?
# @return [TrueClass, FalseClass]
# true if result of operation was successful
def success?
if self.response.status == 200 || self.response.status == 201
return true
else
return false
end
end
##
# Extracts error messages from the response body
#
# @!attribute [r] error_message
# @return [String]
# error message, if available
def error_message
if self.data?
if self.data.respond_to?(:error) &&
self.data.error.respond_to?(:message)
# You're going to get a terrible error message if the response isn't
# parsed successfully as an error.
return self.data.error.message
elsif self.data['error'] && self.data['error']['message']
return self.data['error']['message']
end
end
return self.body
end
##
# Check for parsable data in response
#
# @!attribute [r] data?
# @return [TrueClass, FalseClass]
# true if body can be parsed
def data?
!(self.body.nil? || self.body.empty? || self.media_type != 'application/json')
end
##
# Return parsed version of the response body.
#
# @!attribute [r] data
# @return [Object, Hash, String]
# Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
def data
return @data ||= (begin
if self.data?
media_type = self.media_type
data = self.body
case media_type
when 'application/json'
data = MultiJson.load(data)
# Strip data wrapper, if present
data = data['data'] if data.has_key?('data')
else
raise ArgumentError,
"Content-Type not supported for parsing: #{media_type}"
end
if @request.api_method && @request.api_method.response_schema
# Automatically parse using the schema designated for the
# response of this API method.
data = @request.api_method.response_schema.new(data)
data
else
# Otherwise, return the raw unparsed value.
# This value must be indexable like a Hash.
data
end
end
end)
end
##
# Get the token used for requesting the next page of data
#
# @!attribute [r] next_page_token
# @return [String]
# next page token
def next_page_token
if self.data.respond_to?(:next_page_token)
return self.data.next_page_token
elsif self.data.respond_to?(:[])
return self.data["nextPageToken"]
else
raise TypeError, "Data object did not respond to #next_page_token."
end
end
##
# Build a request for fetching the next page of data
#
# @return [Google::APIClient::Request]
# API request for retrieving next page, nil if no page token available
def next_page
return nil unless self.next_page_token
merged_parameters = Hash[self.reference.parameters].merge({
self.page_token_param => self.next_page_token
})
# Because Requests can be coerced to Hashes, we can merge them,
# preserving all context except the API method parameters that we're
# using for pagination.
return Google::APIClient::Request.new(
Hash[self.reference].merge(:parameters => merged_parameters)
)
end
##
# Get the token used for requesting the previous page of data
#
# @!attribute [r] prev_page_token
# @return [String]
# previous page token
def prev_page_token
if self.data.respond_to?(:prev_page_token)
return self.data.prev_page_token
elsif self.data.respond_to?(:[])
return self.data["prevPageToken"]
else
raise TypeError, "Data object did not respond to #next_page_token."
end
end
##
# Build a request for fetching the previous page of data
#
# @return [Google::APIClient::Request]
# API request for retrieving previous page, nil if no page token available
def prev_page
return nil unless self.prev_page_token
merged_parameters = Hash[self.reference.parameters].merge({
self.page_token_param => self.prev_page_token
})
# Because Requests can be coerced to Hashes, we can merge them,
# preserving all context except the API method parameters that we're
# using for pagination.
return Google::APIClient::Request.new(
Hash[self.reference].merge(:parameters => merged_parameters)
)
end
##
# Pagination scheme used by this request/response
#
# @!attribute [r] pagination_type
# @return [Symbol]
# currently always :token
def pagination_type
return :token
end
##
# Name of the field that contains the pagination token
#
# @!attribute [r] page_token_param
# @return [String]
# currently always 'pageToken'
def page_token_param
return "pageToken"
end
end
end
end

View File

@ -1,235 +0,0 @@
# Copyright 2013 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 'google/api_client'
require 'google/api_client/service/stub_generator'
require 'google/api_client/service/resource'
require 'google/api_client/service/request'
require 'google/api_client/service/result'
require 'google/api_client/service/batch'
require 'google/api_client/service/simple_file_store'
module Google
class APIClient
##
# Experimental new programming interface at the API level.
# Hides Google::APIClient. Designed to be easier to use, with less code.
#
# @example
# calendar = Google::APIClient::Service.new('calendar', 'v3')
# result = calendar.events.list('calendarId' => 'primary').execute()
class Service
include Google::APIClient::Service::StubGenerator
extend Forwardable
DEFAULT_CACHE_FILE = 'discovery.cache'
# Cache for discovered APIs.
@@discovered = {}
##
# Creates a new Service.
#
# @param [String, Symbol] api_name
# The name of the API this service will access.
# @param [String, Symbol] api_version
# The version of the API this service will access.
# @param [Hash] options
# The configuration parameters for the service.
# @option options [Symbol, #generate_authenticated_request] :authorization
# (:oauth_1)
# The authorization mechanism used by the client. The following
# mechanisms are supported out-of-the-box:
# <ul>
# <li><code>:two_legged_oauth_1</code></li>
# <li><code>:oauth_1</code></li>
# <li><code>:oauth_2</code></li>
# </ul>
# @option options [Boolean] :auto_refresh_token (true)
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute. If the token does
# not support it, this option is ignored.
# @option options [String] :application_name
# The name of the application using the client.
# @option options [String] :application_version
# The version number of the application using the client.
# @option options [String] :host ("www.googleapis.com")
# The API hostname used by the client. This rarely needs to be changed.
# @option options [String] :port (443)
# The port number used by the client. This rarely needs to be changed.
# @option options [String] :discovery_path ("/discovery/v1")
# The discovery base path. This rarely needs to be changed.
# @option options [String] :ca_file
# Optional set of root certificates to use when validating SSL connections.
# By default, a bundled set of trusted roots will be used.
# @option options [#generate_authenticated_request] :authorization
# The authorization mechanism for requests. Used only if
# `:authenticated` is `true`.
# @option options [TrueClass, FalseClass] :authenticated (default: true)
# `true` if requests must be signed or somehow
# authenticated, `false` otherwise.
# @option options [TrueClass, FalseClass] :gzip (default: true)
# `true` if gzip enabled, `false` otherwise.
# @options options[Hash] :faraday_option
# Pass through of options to set on the Faraday connection
# @option options [Faraday::Connection] :connection
# A custom connection to be used for all requests.
# @option options [ActiveSupport::Cache::Store, :default] :discovery_cache
# A cache store to place the discovery documents for loaded APIs.
# Avoids unnecessary roundtrips to the discovery service.
# :default loads the default local file cache store.
def initialize(api_name, api_version, options = {})
@api_name = api_name.to_s
if api_version.nil?
raise ArgumentError,
"API version must be set"
end
@api_version = api_version.to_s
if options && !options.respond_to?(:to_hash)
raise ArgumentError,
"expected options Hash, got #{options.class}"
end
params = {}
[:application_name, :application_version, :authorization, :host, :port,
:discovery_path, :auto_refresh_token, :key, :user_ip,
:ca_file, :faraday_option].each do |option|
if options.include? option
params[option] = options[option]
end
end
@client = Google::APIClient.new(params)
@connection = options[:connection] || @client.connection
@options = options
# Initialize cache store. Default to SimpleFileStore if :cache_store
# is not provided and we have write permissions.
if options.include? :cache_store
@cache_store = options[:cache_store]
else
cache_exists = File.exists?(DEFAULT_CACHE_FILE)
if (cache_exists && File.writable?(DEFAULT_CACHE_FILE)) ||
(!cache_exists && File.writable?(Dir.pwd))
@cache_store = Google::APIClient::Service::SimpleFileStore.new(
DEFAULT_CACHE_FILE)
end
end
# Attempt to read API definition from memory cache.
# Not thread-safe, but the worst that can happen is a cache miss.
unless @api = @@discovered[[api_name, api_version]]
# Attempt to read API definition from cache store, if there is one.
# If there's a miss or no cache store, call discovery service.
if !@cache_store.nil?
@api = @cache_store.fetch("%s/%s" % [api_name, api_version]) do
@client.discovered_api(api_name, api_version)
end
else
@api = @client.discovered_api(api_name, api_version)
end
@@discovered[[api_name, api_version]] = @api
end
generate_call_stubs(self, @api)
end
##
# Returns the authorization mechanism used by the service.
#
# @return [#generate_authenticated_request] The authorization mechanism.
def_delegators :@client, :authorization, :authorization=
##
# The setting that controls whether or not the service attempts to
# refresh authorization when a 401 is hit during an API call.
#
# @return [Boolean]
def_delegators :@client, :auto_refresh_token, :auto_refresh_token=
##
# The application's API key issued by the API console.
#
# @return [String] The API key.
def_delegators :@client, :key, :key=
##
# The Faraday/HTTP connection used by this service.
#
# @return [Faraday::Connection]
attr_accessor :connection
##
# The cache store used for storing discovery documents.
#
# @return [ActiveSupport::Cache::Store,
# Google::APIClient::Service::SimpleFileStore,
# nil]
attr_reader :cache_store
##
# Prepares a Google::APIClient::BatchRequest object to make batched calls.
# @param [Array] calls
# Optional array of Google::APIClient::Service::Request to initialize
# the batch request with.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call defined
# a callback of its own.
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def batch(calls = nil, &block)
Google::APIClient::Service::BatchRequest.new(self, calls, &block)
end
##
# Executes an API request.
# Do not call directly; this method is only used by Request objects when
# executing.
#
# @param [Google::APIClient::Service::Request,
# Google::APIClient::Service::BatchCall] request
# The request to be executed.
def execute(request)
if request.instance_of? Google::APIClient::Service::Request
params = {:api_method => request.method,
:parameters => request.parameters,
:connection => @connection}
if request.respond_to? :body
if request.body.respond_to? :to_hash
params[:body_object] = request.body
else
params[:body] = request.body
end
end
if request.respond_to? :media
params[:media] = request.media
end
[:authenticated, :gzip].each do |option|
if @options.include? option
params[option] = @options[option]
end
end
result = @client.execute(params)
return Google::APIClient::Service::Result.new(request, result)
elsif request.instance_of? Google::APIClient::Service::BatchRequest
@client.execute(request.base_batch, {:connection => @connection})
end
end
end
end
end

View File

@ -1,110 +0,0 @@
# Copyright 2013 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 'google/api_client/service/result'
require 'google/api_client/batch'
module Google
class APIClient
class Service
##
# Helper class to contain the result of an individual batched call.
#
class BatchedCallResult < Result
# @return [Fixnum] Index of the call
def call_index
return @base_result.response.call_id.to_i - 1
end
end
##
#
#
class BatchRequest
##
# Creates a new batch request.
# This class shouldn't be instantiated directly, but rather through
# Service.batch.
#
# @param [Array] calls
# List of Google::APIClient::Service::Request to be made.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call
# defined a callback of its own.
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def initialize(service, calls, &block)
@service = service
@base_batch = Google::APIClient::BatchRequest.new
@global_callback = block if block_given?
if calls && calls.length > 0
calls.each do |call|
add(call)
end
end
end
##
# Add a new call to the batch request.
#
# @param [Google::APIClient::Service::Request] call
# the call to be added.
# @param [Proc] block
# callback for this call's response.
#
# @return [Google::APIClient::Service::BatchRequest]
# the BatchRequest, for chaining
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def add(call, &block)
if !block_given? && @global_callback.nil?
raise BatchError, 'Request needs a block'
end
callback = block || @global_callback
base_call = {
:api_method => call.method,
:parameters => call.parameters
}
if call.respond_to? :body
if call.body.respond_to? :to_hash
base_call[:body_object] = call.body
else
base_call[:body] = call.body
end
end
@base_batch.add(base_call) do |base_result|
result = Google::APIClient::Service::BatchedCallResult.new(
call, base_result)
callback.call(result)
end
return self
end
##
# Executes the batch request.
def execute
@service.execute(self)
end
attr_reader :base_batch
end
end
end
end

View File

@ -1,144 +0,0 @@
# Copyright 2013 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.
module Google
class APIClient
class Service
##
# Handles an API request.
# This contains a full definition of the request to be made (including
# method name, parameters, body and media). The remote API call can be
# invoked with execute().
class Request
##
# Build a request.
# This class should not be directly instantiated in user code;
# instantiation is handled by the stub methods created on Service and
# Resource objects.
#
# @param [Google::APIClient::Service] service
# The parent Service instance that will execute the request.
# @param [Google::APIClient::Method] method
# The Method instance that describes the API method invoked by the
# request.
# @param [Hash] parameters
# A Hash of parameter names and values to be sent in the API call.
def initialize(service, method, parameters)
@service = service
@method = method
@parameters = parameters
@body = nil
@media = nil
metaclass = (class << self; self; end)
# If applicable, add "body", "body=" and resource-named methods for
# retrieving and setting the HTTP body for this request.
# Examples of setting the body for files.insert in the Drive API:
# request.body = object
# request.execute
# OR
# request.file = object
# request.execute
# OR
# request.body(object).execute
# OR
# request.file(object).execute
# Examples of retrieving the body for files.insert in the Drive API:
# object = request.body
# OR
# object = request.file
if method.request_schema
body_name = method.request_schema.data['id'].dup
body_name[0] = body_name[0].chr.downcase
body_name_equals = (body_name + '=').to_sym
body_name = body_name.to_sym
metaclass.send(:define_method, :body) do |*args|
if args.length == 1
@body = args.first
return self
elsif args.length == 0
return @body
else
raise ArgumentError,
"wrong number of arguments (#{args.length}; expecting 0 or 1)"
end
end
metaclass.send(:define_method, :body=) do |body|
@body = body
end
metaclass.send(:alias_method, body_name, :body)
metaclass.send(:alias_method, body_name_equals, :body=)
end
# If applicable, add "media" and "media=" for retrieving and setting
# the media object for this request.
# Examples of setting the media object:
# request.media = object
# request.execute
# OR
# request.media(object).execute
# Example of retrieving the media object:
# object = request.media
if method.media_upload
metaclass.send(:define_method, :media) do |*args|
if args.length == 1
@media = args.first
return self
elsif args.length == 0
return @media
else
raise ArgumentError,
"wrong number of arguments (#{args.length}; expecting 0 or 1)"
end
end
metaclass.send(:define_method, :media=) do |media|
@media = media
end
end
end
##
# Returns the parent service capable of executing this request.
#
# @return [Google::APIClient::Service] The parent service.
attr_reader :service
##
# Returns the Method instance that describes the API method invoked by
# the request.
#
# @return [Google::APIClient::Method] The API method description.
attr_reader :method
##
# Contains the Hash of parameter names and values to be sent as the
# parameters for the API call.
#
# @return [Hash] The request parameters.
attr_accessor :parameters
##
# Executes the request.
def execute
@service.execute(self)
end
end
end
end
end

View File

@ -1,40 +0,0 @@
# Copyright 2013 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.
module Google
class APIClient
class Service
##
# Handles an API resource.
# Simple class that contains API methods and/or child resources.
class Resource
include Google::APIClient::Service::StubGenerator
##
# Build a resource.
# This class should not be directly instantiated in user code; resources
# are instantiated by the stub generation mechanism on Service creation.
#
# @param [Google::APIClient::Service] service
# The Service instance this resource belongs to.
# @param [Google::APIClient::API, Google::APIClient::Resource] root
# The node corresponding to this resource.
def initialize(service, root)
@service = service
generate_call_stubs(service, root)
end
end
end
end
end

View File

@ -1,164 +0,0 @@
# Copyright 2013 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.
module Google
class APIClient
class Service
##
# Handles an API result.
# Wraps around the Google::APIClient::Result class, making it easier to
# handle the result (e.g. pagination) and keeping it in line with the rest
# of the Service programming interface.
class Result
extend Forwardable
##
# Init the result.
#
# @param [Google::APIClient::Service::Request] request
# The original request
# @param [Google::APIClient::Result] base_result
# The base result to be wrapped
def initialize(request, base_result)
@request = request
@base_result = base_result
end
# @!attribute [r] status
# @return [Fixnum] HTTP status code
# @!attribute [r] headers
# @return [Hash] HTTP response headers
# @!attribute [r] body
# @return [String] HTTP response body
def_delegators :@base_result, :status, :headers, :body
# @return [Google::APIClient::Service::Request] Original request object
attr_reader :request
##
# Get the content type of the response
# @!attribute [r] media_type
# @return [String]
# Value of content-type header
def_delegators :@base_result, :media_type
##
# Check if request failed
#
# @!attribute [r] error?
# @return [TrueClass, FalseClass]
# true if result of operation is an error
def_delegators :@base_result, :error?
##
# Check if request was successful
#
# @!attribute [r] success?
# @return [TrueClass, FalseClass]
# true if result of operation was successful
def_delegators :@base_result, :success?
##
# Extracts error messages from the response body
#
# @!attribute [r] error_message
# @return [String]
# error message, if available
def_delegators :@base_result, :error_message
##
# Check for parsable data in response
#
# @!attribute [r] data?
# @return [TrueClass, FalseClass]
# true if body can be parsed
def_delegators :@base_result, :data?
##
# Return parsed version of the response body.
#
# @!attribute [r] data
# @return [Object, Hash, String]
# Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
def_delegators :@base_result, :data
##
# Pagination scheme used by this request/response
#
# @!attribute [r] pagination_type
# @return [Symbol]
# currently always :token
def_delegators :@base_result, :pagination_type
##
# Name of the field that contains the pagination token
#
# @!attribute [r] page_token_param
# @return [String]
# currently always 'pageToken'
def_delegators :@base_result, :page_token_param
##
# Get the token used for requesting the next page of data
#
# @!attribute [r] next_page_token
# @return [String]
# next page tokenx =
def_delegators :@base_result, :next_page_token
##
# Get the token used for requesting the previous page of data
#
# @!attribute [r] prev_page_token
# @return [String]
# previous page token
def_delegators :@base_result, :prev_page_token
# @!attribute [r] resumable_upload
def resumable_upload
# TODO(sgomes): implement resumable_upload for Service::Result
raise NotImplementedError
end
##
# Build a request for fetching the next page of data
#
# @return [Google::APIClient::Service::Request]
# API request for retrieving next page
def next_page
return nil unless self.next_page_token
request = @request.clone
# Make a deep copy of the parameters.
request.parameters = Marshal.load(Marshal.dump(request.parameters))
request.parameters[page_token_param] = self.next_page_token
return request
end
##
# Build a request for fetching the previous page of data
#
# @return [Google::APIClient::Service::Request]
# API request for retrieving previous page
def prev_page
return nil unless self.prev_page_token
request = @request.clone
# Make a deep copy of the parameters.
request.parameters = Marshal.load(Marshal.dump(request.parameters))
request.parameters[page_token_param] = self.prev_page_token
return request
end
end
end
end
end

View File

@ -1,151 +0,0 @@
# Copyright 2013 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.
module Google
class APIClient
class Service
# Simple file store to be used in the event no ActiveSupport cache store
# is provided. This is not thread-safe, and does not support a number of
# features (such as expiration), but it's useful for the simple purpose of
# caching discovery documents to disk.
# Implements the basic cache methods of ActiveSupport::Cache::Store in a
# limited fashion.
class SimpleFileStore
# Creates a new SimpleFileStore.
#
# @param [String] file_path
# The path to the cache file on disk.
# @param [Object] options
# The options to be used with this SimpleFileStore. Not implemented.
def initialize(file_path, options = nil)
@file_path = file_path.to_s
end
# Returns true if a key exists in the cache.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def exist?(name, options = nil)
read_file
@cache.nil? ? nil : @cache.include?(name.to_s)
end
# Fetches data from the cache and returns it, using the given key.
# If the key is missing and no block is passed, returns nil.
# If the key is missing and a block is passed, executes the block, sets
# the key to its value, and returns it.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
# @yield [String]
# optional block with the default value if the key is missing
def fetch(name, options = nil)
read_file
if block_given?
entry = read(name.to_s, options)
if entry.nil?
value = yield name.to_s
write(name.to_s, value)
return value
else
return entry
end
else
return read(name.to_s, options)
end
end
# Fetches data from the cache, using the given key.
# Returns nil if the key is missing.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def read(name, options = nil)
read_file
@cache.nil? ? nil : @cache[name.to_s]
end
# Writes the value to the cache, with the key.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] value
# The value to be written.
# @param [Object] options
# The options to be used with this query. Not implemented.
def write(name, value, options = nil)
read_file
@cache = {} if @cache.nil?
@cache[name.to_s] = value
write_file
return nil
end
# Deletes an entry in the cache.
# Returns true if an entry is deleted.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def delete(name, options = nil)
read_file
return nil if @cache.nil?
if @cache.include? name.to_s
@cache.delete name.to_s
write_file
return true
else
return nil
end
end
protected
# Read the entire cache file from disk.
# Will avoid reading if there have been no changes.
def read_file
if !File.exist? @file_path
@cache = nil
else
# Check for changes after our last read or write.
if @last_change.nil? || File.mtime(@file_path) > @last_change
File.open(@file_path) do |file|
@cache = Marshal.load(file)
@last_change = file.mtime
end
end
end
return @cache
end
# Write the entire cache contents to disk.
def write_file
File.open(@file_path, 'w') do |file|
Marshal.dump(@cache, file)
end
@last_change = File.mtime(@file_path)
end
end
end
end
end

View File

@ -1,61 +0,0 @@
# Copyright 2013 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 'active_support/inflector'
module Google
class APIClient
class Service
##
# Auxiliary mixin to generate resource and method stubs.
# Used by the Service and Service::Resource classes to generate both
# top-level and nested resources and methods.
module StubGenerator
def generate_call_stubs(service, root)
metaclass = (class << self; self; end)
# Handle resources.
root.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) do
Google::APIClient::Service::Resource.new(service, resource)
end
end
end
# Handle methods.
root.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) do |*args|
if args.length > 1
raise ArgumentError,
"wrong number of arguments (#{args.length} for 1)"
elsif !args.first.respond_to?(:to_hash) && !args.first.nil?
raise ArgumentError,
"expected parameter Hash, got #{args.first.class}"
else
return Google::APIClient::Service::Request.new(
service, method, args.first
)
end
end
end
end
end
end
end
end
end

48
lib/google/apis.rb Normal file
View File

@ -0,0 +1,48 @@
# Copyright 2015 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 'google/apis/version'
require 'logger'
module Google
module Apis
ROOT = File.expand_path('..', File.dirname(__dir__))
# @!attribute [rw] logger
# @return [Logger] The logger.
def self.logger
@logger ||= rails_logger || default_logger
end
class << self
attr_writer :logger
end
private
# Create and configure a logger
# @return [Logger]
def self.default_logger
logger = Logger.new($stdout)
logger.level = Logger::WARN
logger
end
# Check to see if client is being used in a Rails environment and ge the logger if present
# @return [Logger]
def self.rails_logger
::Rails.logger if defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
end
end
end

View File

@ -0,0 +1,128 @@
# Copyright 2015 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 'active_support/inflector'
require 'addressable/uri'
require 'addressable/template'
require 'google/apis/core/http_command'
require 'google/apis/errors'
require 'multi_json'
require 'retriable'
module Google
module Apis
module Core
# Command for executing most basic API request with JSON requests/responses
class ApiCommand < HttpCommand
JSON_CONTENT_TYPE = 'application/json'
FIELDS_PARAM = 'fields'
RATE_LIMIT_ERRORS = %w(rateLimitExceeded userRateLimitExceeded)
# JSON serializer for request objects
# @return [Google::Apis::Core::JsonRepresentation]
attr_accessor :request_representation
# Request body to serialize
# @return [Object]
attr_accessor :request_object
# JSON serializer for response objects
# @return [Google::Apis::Core::JsonRepresentation]
attr_accessor :response_representation
# Class to instantiate when de-serializing responses
# @return [Object]
attr_accessor :response_class
# Serialize the request body
#
# @return [void]
def prepare!
query[FIELDS_PARAM] = normalize_fields_param(query[FIELDS_PARAM]) if query.key?(FIELDS_PARAM)
if request_representation && request_object
header[:content_type] ||= JSON_CONTENT_TYPE
self.body = request_representation.new(request_object).to_json(skip_undefined: true)
end
super
end
# Deserialize the response body if present
#
# @param [String] content_type
# Content type of body
# @param [String, #read] body
# Response body
# @return [Object]
# Response object
# noinspection RubyUnusedLocalVariable
def decode_response_body(content_type, body)
return super unless response_representation
return super if content_type.nil?
return nil unless content_type.start_with?(JSON_CONTENT_TYPE)
instance = response_class.new
response_representation.new(instance).from_json(body, unwrap: response_class)
instance
end
# Check the response and raise error if needed
#
# @param [Fixnum] status
# HTTP status code of response
# @param [String] body
# HTTP response body
# @return [void]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def check_status(status, body = nil)
case status
when 400, 402...500
error = parse_error(body)
if error
logger.debug { error }
fail Google::Apis::RateLimitError, error if RATE_LIMIT_ERRORS.include?(error['reason'])
end
super(status, error)
else
super(status, body)
end
end
private
# Attempt to parse a JSON error message, returning the first found error
# @param [String] body
# HTTP response body
# @return [Hash]
def parse_error(body)
hash = MultiJson.load(body)
hash['error']['errors'].first
rescue
nil
end
# Convert field names from ruby conventions to original names in JSON
#
# @param [String] fields
# Value of 'fields' param
# @return [String]
# Updated header value
def normalize_fields_param(fields)
# TODO: Generate map of parameter names during code gen. Small possibility that camelization fails
fields.gsub(/:/, '').gsub(/[\w_]+/) { |str| ActiveSupport::Inflector.camelize(str, false) }
end
end
end
end
end

View File

@ -0,0 +1,314 @@
# Copyright 2015 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 'addressable/uri'
require 'addressable/template'
require 'google/apis/version'
require 'google/apis/core/api_command'
require 'google/apis/core/batch'
require 'google/apis/core/upload'
require 'google/apis/core/download'
require 'google/apis/options'
require 'googleauth'
require 'hurley'
require 'hurley/addressable'
module Google
module Apis
module Core
# Base service for all APIs. Not to be used directly.
#
class BaseService
# Root URL (host/port) for the API
# @return [Addressable::URI]
attr_accessor :root_url
# Additional path prefix for all API methods
# @return [Addressable::URI]
attr_accessor :base_path
# Alternate path prefix for media uploads
# @return [Addressable::URI]
attr_accessor :upload_path
# Alternate path prefix for all batch methods
# @return [Addressable::URI]
attr_accessor :batch_path
# HTTP client
# @return [Hurley::Client]
attr_accessor :client
# General settings
# @return [Google::Apis::ClientOptions]
attr_accessor :client_options
# Default options for all requests
# @return [Google::Apis::RequestOptions]
attr_accessor :request_options
# @param [String,Addressable::URI] root_url
# Root URL for the API
# @param [String,Addressable::URI] base_path
# Additional path prefix for all API methods
# @api private
def initialize(root_url, base_path)
self.root_url = root_url
self.base_path = base_path
self.upload_path = "upload/#{base_path}"
self.batch_path = 'batch'
self.client_options = Google::Apis::ClientOptions.default.dup
self.request_options = Google::Apis::RequestOptions.default.dup
end
# @!attribute [rw] authorization
# @return [Signet::OAuth2::Client]
# OAuth2 credentials
def authorization=(authorization)
request_options.authorization = authorization
end
def authorization
request_options.authorization
end
# TODO: with(options) method
# Perform a batch request. Calls made within the block are sent in a single network
# request to the server.
#
# @example
# service.batch do |service|
# service.get_item(id1) do |res, err|
# # process response for 1st call
# end
# # ...
# service.get_item(idN) do |res, err|
# # process response for Nth call
# end
# end
#
# @param [Hash, Google::Apis::RequestOptions] options
# Request-specific options
# @yield [self]
# @return [void]
def batch(options = nil)
batch_command = BatchCommand.new(:post, Addressable::URI.parse(root_url + batch_path))
batch_command.options = request_options.merge(options)
apply_command_defaults(batch_command)
begin
Thread.current[:google_api_batch] = batch_command
yield self
ensure
Thread.current[:google_api_batch] = nil
end
batch_command.execute(client)
end
# Perform a batch upload request. Calls made within the block are sent in a single network
# request to the server. Batch uploads are useful for uploading multiple small files. For larger
# files, use single requests which use a resumable upload protocol.
#
# @example
# service.batch do |service|
# service.insert_item(upload_source: 'file1.txt') do |res, err|
# # process response for 1st call
# end
# # ...
# service.insert_item(upload_source: 'fileN.txt') do |res, err|
# # process response for Nth call
# end
# end
#
# @param [Hash, Google::Apis::RequestOptions] options
# Request-specific options
# @yield [self]
# @return [void]
def batch_upload(options = nil)
batch_command = BatchUploadCommand.new(:put, Addressable::URI.parse(root_url + upload_path))
batch_command.options = request_options.merge(options)
apply_command_defaults(batch_command)
begin
Thread.current[:google_api_batch] = batch_command
yield self
ensure
Thread.current[:google_api_batch] = nil
end
batch_command.execute(client)
end
# Get the current HTTP client
# @return [Hurley::Client]
def client
@client ||= new_client
end
# Simple escape hatch for making API requests directly to a given
# URL. This is not intended to be used as a generic HTTP client
# and should be used only in cases where no service method exists
# (e.g. fetching an export link for a Google Drive file.)
#
# @param [Symbol] method
# HTTP method as symbol (e.g. :get, :post, :put, ...)
# @param [String] url
# URL to call
# @param [Hash<String,String] params
# Optional hash of query parameters
# @param [#read] body
# Optional body for POST/PUT
# @param [IO, String] download_dest
# IO stream or filename to receive content download
# @param [Google::Apis::RequestOptions] options
# Request-specific options
#
# @yield [result, err] Result & error if block supplied
# @yieldparam result [String] HTTP response body
# @yieldparam err [StandardError] error object if request failed
#
# @return [String] HTTP response body
def http(method, url, params: nil, body: nil, download_dest: nil, options: nil, &block)
if download_dest
command = DownloadCommand.new(method, url, body: body)
else
command = HttpCommand.new(method, url, body: body)
end
command.options = request_options.merge(options)
apply_command_defaults(command)
command.query.merge(Hash(params))
execute_or_queue_command(command, &block)
end
protected
# Create a new upload command.
#
# @param [symbol] method
# HTTP method for uploading (typically :put or :post)
# @param [String] path
# Additional path to upload endpoint, appended to API base path
# @param [Hash, Google::Apis::RequestOptions] options
# Request-specific options
# @return [Google::Apis::Core::UploadCommand]
def make_upload_command(method, path, options)
template = Addressable::Template.new(root_url + upload_path + path)
if batch?
command = MultipartUploadCommand.new(method, template)
else
command = ResumableUploadCommand.new(method, template)
end
command.options = request_options.merge(options)
apply_command_defaults(command)
command
end
# Create a new download command.
#
# @param [symbol] method
# HTTP method for uploading (typically :get)
# @param [String] path
# Additional path to download endpoint, appended to API base path
# @param [Hash, Google::Apis::RequestOptions] options
# Request-specific options
# @return [Google::Apis::Core::DownloadCommand]
def make_download_command(method, path, options)
template = Addressable::Template.new(root_url + base_path + path)
command = DownloadCommand.new(method, template)
command.options = request_options.merge(options)
command.query['alt'] = 'media'
apply_command_defaults(command)
command
end
# Create a new command.
#
# @param [symbol] method
# HTTP method (:get, :post, :delete, etc...)
# @param [String] path
# Additional path, appended to API base path
# @param [Hash, Google::Apis::RequestOptions] options
# Request-specific options
# @return [Google::Apis::Core::DownloadCommand]
def make_simple_command(method, path, options)
template = Addressable::Template.new(root_url + base_path + path)
command = ApiCommand.new(method, template)
command.options = request_options.merge(options)
apply_command_defaults(command)
command
end
# Execute the request. If a batch is in progress, the request is added to the batch instead.
#
# @param [Google::Apis::Core::HttpCommand] command
# Command to execute
# @return [Object] response object if command executed and no callback supplied
# @yield [result, err] Result & error if block supplied
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute_or_queue_command(command, &callback)
batch_command = current_batch
if batch_command
batch_command.add(command, &callback)
nil
else
command.execute(client, &callback)
end
end
# Update commands with service-specific options. To be implemented by subclasses
# @param [Google::Apis::Core::HttpCommand] _command
def apply_command_defaults(_command)
end
private
# Get the current batch context
#
# @return [Google:Apis::Core::BatchRequest]
def current_batch
Thread.current[:google_api_batch]
end
# Check if a batch is in progress
# @return [Boolean]
def batch?
!current_batch.nil?
end
# Create a new HTTP client
# @return [Hurley::Client]
def new_client
client = Hurley::Client.new
client.request_options.timeout = request_options.timeout_sec
client.request_options.proxy = client_options.proxy_url
client.request_options.query_class = Hurley::Query::Flat
client.ssl_options.ca_file = File.join(Google::Apis::ROOT, 'lib', 'cacerts.pem')
client.header[:user_agent] = user_agent
client
end
# Build the user agent header
# @return [String]
def user_agent
sprintf('%s/%s google-api-ruby-client/%s %s (gzip)',
client_options.application_name,
client_options.application_version,
Google::Apis::VERSION,
Google::Apis::OS_VERSION)
end
end
end
end
end

View File

@ -0,0 +1,222 @@
# Copyright 2015 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.
# Copyright 2015 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 'hurley'
require 'google/apis/core/multipart'
require 'google/apis/core/http_command'
require 'google/apis/core/upload'
require 'google/apis/core/download'
require 'addressable/uri'
module Google
module Apis
module Core
# Wrapper request for batching multiple calls in a single server request
class BatchCommand < HttpCommand
BATCH_BOUNDARY = 'RubyApiBatchRequest'.freeze
MULTIPART_MIXED = 'multipart/mixed'
# @param [symbol] method
# HTTP method
# @param [String,Addressable::URI, Addressable::Template] url
# HTTP URL or template
def initialize(method, url)
super(method, url)
@calls = []
end
##
# Add a new call to the batch request.
#
# @param [Google::Apis::Core::HttpCommand] call API Request to add
# @yield [result, err] Result & error when response available
# @return [Google::Apis::Core::BatchCommand] self
def add(call, &block)
ensure_valid_command(call)
@calls << [call, block]
self
end
protected
##
# Deconstruct the batch response and process the individual results
#
# @param [String] content_type
# Content type of body
# @param [String, #read] body
# Response body
# @return [Object]
# Response object
def decode_response_body(content_type, body)
m = /.*boundary=(.+)/.match(content_type)
if m
parts = split_parts(body, m[1])
deserializer = CallDeserializer.new
parts.each_index do |index|
call, callback = @calls[index]
begin
result = call.process_response(*deserializer.to_http_response(parts[index])) unless call.nil?
success(result, &callback)
rescue => e
error(e, &callback)
end
end
end
nil
end
def split_parts(body, boundary)
parts = body.split(/\r?\n?--#{Regexp.escape(boundary)}/)
parts[1...-1]
end
# Encode the batch request
# @return [void]
# @raise [Google::Apis::BatchError] if batch is empty
def prepare!
fail BatchError, 'Cannot make an empty batch request' if @calls.empty?
serializer = CallSerializer.new
multipart = Multipart.new(boundary: BATCH_BOUNDARY, content_type: MULTIPART_MIXED)
@calls.each do |(call, _)|
io = serializer.to_upload_io(call)
multipart.add_upload(io)
end
self.body = multipart.assemble
header[:content_type] = multipart.content_type
header[:content_length] = "#{body.length}"
super
end
def ensure_valid_command(command)
if command.is_a?(Google::Apis::Core::BaseUploadCommand) || command.is_a?(Google::Apis::Core::DownloadCommand)
fail Google::Apis::ClientError, 'Can not include media requests in batch'
end
fail Google::Apis::ClientError, 'Invalid command object' unless command.is_a?(HttpCommand)
end
end
# Wrapper request for batching multiple uploads in a single server request
class BatchUploadCommand < BatchCommand
def ensure_valid_command(command)
fail Google::Apis::ClientError, 'Can only include upload commands in batch' \
unless command.is_a?(Google::Apis::Core::BaseUploadCommand)
end
def prepare!
header['X-Goog-Upload-Protocol'] = 'batch'
super
end
end
# Serializes a command for embedding in a multipart batch request
# @private
class CallSerializer
HTTP_CONTENT_TYPE = 'application/http'
##
# Serialize a single batched call for assembling the multipart message
#
# @param [Google::Apis::Core::HttpCommand] call
# the call to serialize.
# @return [Hurley::UploadIO]
# the serialized request
def to_upload_io(call)
call.prepare!
parts = []
parts << build_head(call)
parts << build_body(call) unless call.body.nil?
length = parts.inject(0) { |a, e| a + e.length }
Hurley::UploadIO.new(Hurley::CompositeReadIO.new(length, *parts),
HTTP_CONTENT_TYPE,
'ruby-api-request')
end
protected
def build_head(call)
request_head = "#{call.method.to_s.upcase} #{Addressable::URI.parse(call.url).request_uri} HTTP/1.1"
call.header.each do |key, value|
request_head << sprintf("\r\n%s: %s", key, value)
end
request_head << sprintf("\r\nHost: %s", call.url.host)
request_head << "\r\n\r\n"
StringIO.new(request_head)
end
def build_body(call)
return nil if call.body.nil?
return call.body if call.body.respond_to?(:read)
StringIO.new(call.body)
end
end
# Deconstructs a raw HTTP response part
# @private
class CallDeserializer
# Convert a single batched response into a BatchedCallResponse object.
#
# @param [String] call_response
# the response to parse.
# @return [Array<(Fixnum, Hurley::Header, String)>]
# Status, header, and response body.
def to_http_response(call_response)
_, outer_body = split_header_and_body(call_response)
status_line, payload = outer_body.split(/\n/, 2)
_, status = status_line.split(' ', 3)
header, body = split_header_and_body(payload)
[status.to_i, header, body]
end
protected
# Auxiliary method to split the header from the body in an HTTP response.
#
# @param [String] response
# the response to parse.
# @return [Array<(Hurley::Header, String)>]
# the header and the body, separately.
def split_header_and_body(response)
header = Hurley::Header.new
payload = response.lstrip
while payload
line, payload = payload.split(/\n/, 2)
line.sub!(/\s+\z/, '')
break if line.empty?
match = /\A([^:]+):\s*/.match(line)
fail BatchError, sprintf('Invalid header line in response: %s', line) if match.nil?
header[match[1]] = match.post_match
end
[header, payload]
end
end
end
end
end

View File

@ -0,0 +1,94 @@
# Copyright 2015 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 'google/apis/core/multipart'
require 'google/apis/core/api_command'
require 'google/apis/errors'
require 'addressable/uri'
module Google
module Apis
module Core
# Streaming/resumable media download support
class DownloadCommand < ApiCommand
RANGE_HEADER = 'range'
# File or IO to write content to
# @return [String, File, #write]
attr_accessor :download_dest
# Ensure the download destination is a writable stream.
#
# @return [void]
def prepare!
@state = :start
@download_url = nil
@offset = 0
if download_dest.respond_to?(:write)
@download_io = download_dest
@close_io_on_finish = false
elsif download_dest.is_a?(String)
@download_io = File.open(download_dest, 'wb')
@close_io_on_finish = true
else
@download_io = StringIO.new('', 'wb')
@close_io_on_finish = false
end
super
end
# Close IO stream when command done. Only closes the stream if it was opened by the command.
def release!
@download_io.close if @close_io_on_finish
end
# Execute the upload request once. Overrides the default implementation to handle streaming/chunking
# of file content.
#
# @private
# @param [Hurley::Client] client
# HTTP client
# @yield [result, err] Result or error if block supplied
# @return [Object]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute_once(client, &block)
client.get(@download_url || url) do |req|
apply_request_options(req)
if @offset > 0
logger.debug { sprintf('Resuming download from offset %d', @offset) }
req.header[RANGE_HEADER] = sprintf('bytes=%d-', @offset)
end
req.on_body do |res, chunk|
check_status(res.status_code, chunk) unless res.status_code.nil?
logger.debug { sprintf('Writing chunk (%d bytes)', chunk.length) }
@offset += chunk.length
@download_io.write(chunk)
@download_io.flush
end
end
if @close_io_on_finish
result = nil
else
result = @download_io
end
success(result, &block)
rescue => e
error(e, rethrow: true, &block)
end
end
end
end
end

View File

@ -0,0 +1,44 @@
# Copyright 2015 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.
module Google
module Apis
module Core
# Adds to_hash to objects
module Hashable
# Convert object to hash representation
#
# @return [Hash]
def to_h
Hash[instance_variables.map { |k| [k[1..-1].to_sym, Hashable.process_value(instance_variable_get(k))] }]
end
# Recursively serialize an object
#
# @param [Object] val
# @return [Hash]
def self.process_value(val)
case val
when Hash
Hash[val.map {|k, v| [k.to_sym, Hashable.process_value(v)] }]
when Array
val.map{ |v| Hashable.process_value(v) }
else
val.respond_to?(:to_h) ? val.to_h : val
end
end
end
end
end
end

View File

@ -0,0 +1,275 @@
# Copyright 2015 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 'addressable/uri'
require 'addressable/template'
require 'google/apis/options'
require 'google/apis/errors'
require 'retriable'
require 'hurley'
require 'hurley/addressable'
require 'google/apis/core/logging'
require 'pp'
module Google
module Apis
module Core
# Command for HTTP request/response.
class HttpCommand
include Logging
RETRIABLE_ERRORS = [Google::Apis::ServerError, Google::Apis::RateLimitError, Google::Apis::TransmissionError]
# Request options
# @return [Google::Apis::RequestOptions]
attr_accessor :options
# HTTP request URL
# @return [String, Addressable::URI]
attr_accessor :url
# HTTP headers
# @return [Hurley::Header]
attr_accessor :header
# Request body
# @return [#read]
attr_accessor :body
# HTTP method
# @return [symbol]
attr_accessor :method
# HTTP Client
# @return [Hurley::Client]
attr_accessor :connection
# Query params
# @return [Hash]
attr_accessor :query
# Path params for URL Template
# @return [Hash]
attr_accessor :params
# @param [symbol] method
# HTTP method
# @param [String,Addressable::URI, Addressable::Template] url
# HTTP URL or template
# @param [String, #read] body
# Request body
def initialize(method, url, body: nil)
self.options = Google::Apis::RequestOptions.default.dup
self.url = url
self.url = Addressable::Template.new(url) if url.is_a?(String)
self.method = method
self.header = Hurley::Header.new
self.body = body
self.query = {}
self.params = {}
end
# Execute the command, retrying as necessary
#
# @param [Hurley::Client] client
# HTTP client
# @yield [result, err] Result or error if block supplied
# @return [Object]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute(client)
prepare!
proc = block_given? ? Proc.new : nil
begin
Retriable.retriable tries: options.retries + 1,
base_interval: 1,
multiplier: 2,
on: RETRIABLE_ERRORS do |try|
# This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
# auth to be re-attempted without having to retry all sorts of other failures like
# NotFound, etc
auth_tries = (try == 1 && authorization_refreshable? ? 2 : 1)
Retriable.retriable tries: auth_tries,
on: [Google::Apis::AuthorizationError],
on_retry: proc { |*| refresh_authorization } do
return execute_once(client, &proc)
end
end
rescue => e
raise e if proc.nil?
end
ensure
release!
end
# Refresh the authorization authorization after a 401 error
#
# @private
# @return [void]
def refresh_authorization
# Handled implicitly by auth lib, here in case need to override
logger.debug('Retrying after authentication failure')
end
# Check if attached credentials can be automatically refreshed
# @return [Boolean]
def authorization_refreshable?
options.authorization.respond_to?(:apply!)
end
# Prepare the request (e.g. calculate headers, serialize data, etc) before sending
#
# @private
# @return [void]
def prepare!
header.update(options.header) if options && options.header
self.url = url.expand(params) if url.is_a?(Addressable::Template)
url.query_values = query
end
# Release any resources used by this command
# @private
# @return [void]
def release!
end
# Check the response and either decode body or raise error
#
# @param [Fixnum] status
# HTTP status code of response
# @param [Hurley::Header] header
# Response headers
# @param [String, #read] body
# Response body
# @return [Object]
# Response object
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def process_response(status, header, body)
check_status(status, body)
decode_response_body(header[:content_type], body)
end
# Check the response and raise error if needed
#
# @param [Fixnum] status
# HTTP status code of response
# @param [String] body
# HTTP response body
# @return [void]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def check_status(status, body = nil)
# TODO: 304 Not Modified depends on context...
case status
when 200...300
nil
when 301, 302, 303, 307
fail Google::Apis::RedirectError, header[:location]
when 401
fail Google::Apis::AuthorizationError, body
when 304, 400, 402...500
fail Google::Apis::ClientError, body
when 500...600
fail Google::Apis::ServerError, body
else
logger.warn(sprintf('Encountered unexpected status code %s', status))
fail Google::Apis::TransmissionError, body
end
end
# Process the actual response body. Intended to be overridden by subclasses
#
# @param [String] _content_type
# Content type of body
# @param [String, #read] body
# Response body
# @return [Object]
def decode_response_body(_content_type, body)
body
end
# Process a success response
# @param [Object] result
# Result object
# @return [Object] result if no block given
# @yield [result, nil] if block given
def success(result, &block)
logger.debug { sprintf('Success - %s', PP.pp(result, '')) }
block.call(result, nil) if block_given?
result
end
# Process an error response
# @param [StandardError] err
# Error object
# @param [Boolean] rethrow
# True if error should be raised again after handling
# @return [void]
# @yield [nil, err] if block given
# @raise [StandardError] if no block
def error(err, rethrow: false, &block)
logger.debug { sprintf('Error - %s', PP.pp(err, '')) }
err = Google::Apis::TransmissionError.new(err) if err.is_a?(Hurley::ClientError)
block.call(nil, err) if block_given?
fail err if rethrow || block.nil?
end
# Execute the command once.
#
# @private
# @param [Hurley::Client] client
# HTTP client
# @yield [result, err] Result or error if block supplied
# @return [Object]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute_once(client, &block)
body.rewind if body.respond_to?(:rewind)
begin
logger.debug { sprintf('Sending HTTP %s %s', method, url) }
response = client.send(method, url, body) do |req|
apply_request_options(req)
end
logger.debug { response.status_code }
logger.debug { response.inspect }
response = process_response(response.status_code, response.header, response.body)
success(response, &block)
rescue => e
logger.debug { sprintf('Caught error %s', e) }
error(e, rethrow: true, &block)
end
end
# Update the request with any specified options.
# @param [Hurley::Request] req
# HTTP request
# @return [void]
def apply_request_options(req)
if options.authorization.respond_to?(:apply!)
options.authorization.apply!(req.header)
elsif options.authorization.is_a?(String)
req.header[:authorization] = sprintf('Bearer %s', options.authorization)
end
req.header.update(header)
req.options.timeout = options.timeout_sec
end
end
end
end
end

View File

@ -0,0 +1,122 @@
# Copyright 2015 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 'representable/json'
require 'representable/json/hash'
require 'representable/coercion'
require 'base64'
module Google
module Apis
module Core
# Support for serializing hashes + propery value/nil/unset tracking
# To be included in representers as a feature.
# @private
module JsonRepresentationSupport
def self.included(base)
base.extend(JsonSupport)
end
# @private
module JsonSupport
# Returns a customized getter function for Representable. Allows
# indifferent hash/attribute access.
#
# @param [String] name Property name
# @return [Proc]
def getter_fn(name)
ivar_name = "@#{name}".to_sym
lambda do |_|
if respond_to?(:[])
self[name] || instance_variable_get(ivar_name)
else
instance_variable_get(ivar_name)
end
end
end
# Returns a customized function for Representable that checks whether or not
# an attribute should be serialized. Allows proper patch semantics by distinguishing
# between nil & unset values
#
# @param [String] name Property name
# @return [Proc]
def if_fn(name)
ivar_name = "@#{name}".to_sym
lambda do |opts|
if opts[:skip_undefined]
if respond_to?(:key?)
self.key?(name) || instance_variable_defined?(ivar_name)
else
instance_variable_defined?(ivar_name)
end
else
true
end
end
end
def set_default_options(name, options)
if options[:base64]
options[:render_filter] = ->(value, _doc, *_args) { Base64.urlsafe_encode64(value) }
options[:parse_filter] = ->(fragment, _doc, *_args) { Base64.urlsafe_decode64(fragment) }
end
options[:render_nil] = true
options[:getter] = getter_fn(name)
options[:if] = if_fn(name)
end
# Define a single value property
#
# @param [String] name
# Property name
# @param [Hash] options
def property(name, options = {})
set_default_options(name, options)
super(name, options)
end
# Define a collection property
#
# @param [String] name
# Property name
# @param [Hash] options
def collection(name, options = {})
set_default_options(name, options)
super(name, options)
end
# Define a hash property
#
# @param [String] name
# Property name
# @param [Hash] options
def hash(name, options)
set_default_options(name, options)
super(name, options)
end
end
end
# Base decorator for JSON representers
#
# @see https://github.com/apotonick/representable
class JsonRepresentation < Representable::Decorator
include Representable::JSON
include Representable::Coercion
feature JsonRepresentationSupport
end
end
end
end

View File

@ -1,4 +1,4 @@
# Copyright 2010 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,15 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'google/apis'
module Google module Google
class APIClient module Apis
module VERSION module Core
MAJOR = 0 # Logging support
MINOR = 8 module Logging
TINY = 6 # Get the logger instance
PATCH = nil # @return [Logger]
STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.') def logger
Google::Apis.logger
end
end
end end
end end
end end

View File

@ -0,0 +1,173 @@
# Copyright 2015 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 'hurley'
module Google
module Apis
module Core
# Part of a multipart request for holding JSON data
#
# @private
class JsonPart
include Hurley::Multipart::Part
# @return [Fixnum]
# Length of part
attr_reader :length
# @param [String] boundary
# Multipart boundary
# @param [String] value
# JSON content
def initialize(boundary, value)
@part = build_part(boundary, value)
@length = @part.bytesize
@io = StringIO.new(@part)
end
private
# Format the part
#
# @param [String] boundary
# Multipart boundary
# @param [String] value
# JSON content
# @return [String]
def build_part(boundary, value)
part = ''
part << "--#{boundary}\r\n"
part << "Content-Type: application/json\r\n"
part << "\r\n"
part << "#{value}\r\n"
end
end
# Part of a multipart request for holding arbitrary content. Modified
# from Hurley::Multipart::FilePart to remove Content-Disposition
#
# @private
class FilePart
include Hurley::Multipart::Part
# @return [Fixnum]
# Length of part
attr_reader :length
# @param [String] boundary
# Multipart boundary
# @param [Google::Apis::Core::UploadIO] io
# IO stream
# @param [Hash] header
# Additional headers
def initialize(boundary, io, header = {})
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
@head = build_head(boundary, io.content_type, file_length,
io.respond_to?(:opts) ? io.opts.merge(header) : header)
@length = @head.bytesize + file_length + FOOT.length
@io = Hurley::CompositeReadIO.new(@length, StringIO.new(@head), io, StringIO.new(FOOT))
end
private
# Construct the header for the part
#
# @param [String] boundary
# Multipart boundary
# @param [String] type
# Content type for the part
# @param [Fixnum] content_len
# Length of the part
# @param [Hash] header
# Headers for the part
def build_head(boundary, type, content_len, header)
sprintf(HEAD_FORMAT,
boundary,
content_len.to_i,
header[:content_type] || type,
header[:content_transfer_encoding] || DEFAULT_TR_ENCODING)
end
DEFAULT_TR_ENCODING = 'binary'.freeze
FOOT = "\r\n".freeze
HEAD_FORMAT = <<-END
--%s\r
Content-Length: %d\r
Content-Type: %s\r
Content-Transfer-Encoding: %s\r
\r
END
end
# Helper for building multipart requests
class Multipart
MULTIPART_RELATED = 'multipart/related'
DEFAULT_BOUNDARY = 'RubyApiClientMultiPart'
# @return [String]
# Content type header
attr_reader :content_type
# @param [String] content_type
# Content type for the multipart request
# @param [String] boundary
# Part delimiter
def initialize(content_type: MULTIPART_RELATED, boundary: nil)
@parts = []
@boundary = boundary || DEFAULT_BOUNDARY
@content_type = "#{content_type}; boundary=#{boundary}"
end
# Append JSON data part
#
# @param [String] body
# JSON text
# @return [self]
def add_json(body)
@parts << Google::Apis::Core::JsonPart.new(@boundary, body)
self
end
# Append arbitrary data as a part
#
# @param [Google::Apis::Core::UploadIO] upload_io
# IO stream
# @return [self]
def add_upload(upload_io)
@parts << Google::Apis::Core::FilePart.new(@boundary, upload_io)
self
end
# Assemble the multipart requests
#
# @return [IO]
# IO stream
def assemble
@parts << Hurley::Multipart::EpiloguePart.new(@boundary)
ios = []
len = 0
@parts.each do |part|
len += part.length
ios << part.to_io
end
Hurley::CompositeReadIO.new(len, *ios)
end
end
end
end
end

View File

@ -0,0 +1,275 @@
# Copyright 2015 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 'google/apis/core/multipart'
require 'google/apis/core/http_command'
require 'google/apis/core/api_command'
require 'google/apis/errors'
require 'addressable/uri'
require 'mime-types'
module Google
module Apis
module Core
# Extension of Hurley's UploadIO to add length accessor
class UploadIO < Hurley::UploadIO
OCTET_STREAM_CONTENT_TYPE = 'application/octet-stream'
# Get the length of the stream
# @return [Fixnum]
def length
io.respond_to?(:length) ? io.length : File.size(local_path)
end
# Create a new instance given a file path
# @param [String, File] file_name
# Path to file
# @param [String] content_type
# Optional content type. If nil, will attempt to auto-detect
# @return [Google::Apis::Core::UploadIO]
def self.from_file(file_name, content_type: nil)
if content_type.nil?
type = MIME::Types.of(file_name)
content_type = type.first.content_type unless type.nil? || type.empty?
end
new(file_name, content_type || OCTET_STREAM_CONTENT_TYPE)
end
# Wraps an IO stream in UploadIO
# @param [#read] io
# IO to wrap
# @param [String] content_type
# Optional content type.
# @return [Google::Apis::Core::UploadIO]
def self.from_io(io, content_type: OCTET_STREAM_CONTENT_TYPE)
new(io, content_type)
end
end
# Base upload command. Not intended to be used directly
# @private
class BaseUploadCommand < ApiCommand
UPLOAD_PROTOCOL_HEADER = 'X-Goog-Upload-Protocol'
UPLOAD_CONTENT_TYPE_HEADER = 'X-Goog-Upload-Header-Content-Type'
UPLOAD_CONTENT_LENGTH = 'X-Goog-Upload-Header-Content-Length'
# File name or IO containing the content to upload
# @return [String, File, #read]
attr_accessor :upload_source
# Content type of the upload material
# @return [String]
attr_accessor :upload_content_type
# Content, as UploadIO
# @return [Google::Apis::Core::UploadIO]
attr_accessor :upload_io
# Ensure the content is readable and wrapped in an {{Google::Apis::Core::UploadIO}} instance.
#
# @return [void]
# @raise [Google::Apis::ClientError] if upload source is invalid
def prepare!
super
if upload_source.is_a?(IO) || upload_source.is_a?(StringIO)
self.upload_io = UploadIO.from_io(upload_source, content_type: upload_content_type)
@close_io_on_finish = false
elsif upload_source.is_a?(String)
self.upload_io = UploadIO.from_file(upload_source, content_type: upload_content_type)
@close_io_on_finish = true
else
fail Google::Apis::ClientError, 'Invalid upload source'
end
end
# Close IO stream when command done. Only closes the stream if it was opened by the command.
def release!
upload_io.close if @close_io_on_finish
end
end
# Implementation of the raw upload protocol
class RawUploadCommand < BaseUploadCommand
RAW_PROTOCOL = 'raw'
# Ensure the content is readable and wrapped in an {{Google::Apis::Core::UploadIO}} instance.
#
# @return [void]
# @raise [Google::Apis::ClientError] if upload source is invalid
def prepare!
super
self.body = upload_io
header[UPLOAD_PROTOCOL_HEADER] = RAW_PROTOCOL
header[UPLOAD_CONTENT_TYPE_HEADER] = upload_io.content_type
end
end
# Implementation of the multipart upload protocol
class MultipartUploadCommand < BaseUploadCommand
UPLOAD_BOUNDARY = 'RubyApiClientUpload'
MULTIPART_PROTOCOL = 'multipart'
MULTIPART_RELATED = 'multipart/related'
# Encode the multipart request
#
# @return [void]
# @raise [Google::Apis::ClientError] if upload source is invalid
def prepare!
super
@multipart = Multipart.new(boundary: UPLOAD_BOUNDARY, content_type: MULTIPART_RELATED)
@multipart.add_json(body)
@multipart.add_upload(upload_io)
self.body = @multipart.assemble
header[:content_type] = @multipart.content_type
header[UPLOAD_PROTOCOL_HEADER] = MULTIPART_PROTOCOL
end
end
# Implementation of the resumable upload protocol
class ResumableUploadCommand < BaseUploadCommand
UPLOAD_COMMAND_HEADER = 'X-Goog-Upload-Command'
UPLOAD_OFFSET_HEADER = 'X-Goog-Upload-Offset'
BYTES_RECEIVED_HEADER = 'X-Goog-Upload-Size-Received'
UPLOAD_URL_HEADER = 'X-Goog-Upload-URL'
UPLOAD_STATUS_HEADER = 'X-Goog-Upload-Status'
STATUS_ACTIVE = 'active'
STATUS_FINAL = 'final'
STATUS_CANCELLED = 'cancelled'
RESUMABLE = 'resumable'
START_COMMAND = 'start'
QUERY_COMMAND = 'query'
UPLOAD_COMMAND = 'upload, finalize'
# Reset upload to initial state.
#
# @return [void]
# @raise [Google::Apis::ClientError] if upload source is invalid
def prepare!
@state = :start
@upload_url = nil
@offset = 0
super
end
# Check the to see if the upload is complete or needs to be resumed.
#
# @param [Fixnum] status
# HTTP status code of response
# @param [Hurley::Header] header
# Response headers
# @param [String, #read] body
# Response body
# @return [Object]
# Response object
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def process_response(status, header, body)
@offset = Integer(header[BYTES_RECEIVED_HEADER]) if header.key?(BYTES_RECEIVED_HEADER)
@upload_url = header[UPLOAD_URL_HEADER] if header.key?(UPLOAD_URL_HEADER)
upload_status = header[UPLOAD_STATUS_HEADER]
logger.debug { sprintf('Upload status %s', upload_status) }
if upload_status == STATUS_ACTIVE
@state = :active
elsif upload_status == STATUS_FINAL
@state = :final
elsif upload_status == STATUS_CANCELLED
@state = :cancelled
fail Google::Apis::ClientError, body
end
super(status, header, body)
end
# Send the start command to initiate the upload
#
# @param [Hurley::Client] client
# HTTP client
# @return [Hurley::Response]
# @raise [Google::Apis::ServerError] Unable to send the request
def send_start_command(client)
logger.debug { sprintf('Sending upload start command to %s', url) }
client.send(method, url, body) do |req|
apply_request_options(req)
req.header[UPLOAD_PROTOCOL_HEADER] = RESUMABLE
req.header[UPLOAD_COMMAND_HEADER] = START_COMMAND
req.header[UPLOAD_CONTENT_LENGTH] = upload_io.length.to_s
req.header[UPLOAD_CONTENT_TYPE_HEADER] = upload_io.content_type
end
rescue => e
raise Google::Apis::ServerError, e.message
end
# Query for the status of an incomplete upload
#
# @param [Hurley::Client] client
# HTTP client
# @return [Hurley::Response]
# @raise [Google::Apis::ServerError] Unable to send the request
def send_query_command(client)
logger.debug { sprintf('Sending upload query command to %s', @upload_url) }
client.post(@upload_url, nil) do |req|
apply_request_options(req)
req.header[UPLOAD_COMMAND_HEADER] = QUERY_COMMAND
end
end
# Send the actual content
#
# @param [Hurley::Client] client
# HTTP client
# @return [Hurley::Response]
# @raise [Google::Apis::ServerError] Unable to send the request
def send_upload_command(client)
logger.debug { sprintf('Sending upload command to %s', @upload_url) }
content = upload_io
content.pos = @offset
client.post(@upload_url, content) do |req|
apply_request_options(req)
req.header[UPLOAD_COMMAND_HEADER] = UPLOAD_COMMAND
req.header[UPLOAD_OFFSET_HEADER] = @offset.to_s
end
end
# Execute the upload request once. This will typically perform two HTTP requests -- one to initiate or query
# for the status of the upload, the second to send the (remaining) content.
#
# @private
# @param [Hurley::Client] client
# HTTP client
# @yield [result, err] Result or error if block supplied
# @return [Object]
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute_once(client, &block)
if @state == :start
response = send_start_command(client)
else
response = send_query_command(client)
end
result = process_response(response.status_code, response.header, response.body)
if @state == :active
response = send_upload_command(client)
result = process_response(response.status_code, response.header, response.body)
end
success(result, &block) if @state == :final
rescue => e
error(e, rethrow: true, &block)
end
end
end
end
end

View File

@ -1,4 +1,4 @@
# Copyright 2010 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,54 +12,58 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
module Google module Google
class APIClient module Apis
## # Base error, capable of wrapping another
class Error < StandardError
def initialize(err)
@cause = nil
if err.respond_to?(:backtrace)
super(err.message)
@cause = err
else
super(err.to_s)
end
end
def backtrace
if @cause
@cause.backtrace
else
super
end
end
end
# An error which is raised when there is an unexpected response or other # An error which is raised when there is an unexpected response or other
# transport error that prevents an operation from succeeding. # transport error that prevents an operation from succeeding.
class TransmissionError < StandardError class TransmissionError < Error
attr_reader :result
def initialize(message = nil, result = nil)
super(message)
@result = result
end
end end
##
# An exception that is raised if a redirect is required # An exception that is raised if a redirect is required
# #
class RedirectError < TransmissionError class RedirectError < Error
end end
##
# An exception that is raised if a method is called with missing or
# invalid parameter values.
class ValidationError < StandardError
end
##
# A 4xx class HTTP error occurred. # A 4xx class HTTP error occurred.
class ClientError < TransmissionError class ClientError < Error
end
# A 4xx class HTTP error occurred.
class RateLimitError < Error
end end
##
# A 401 HTTP error occurred. # A 401 HTTP error occurred.
class AuthorizationError < ClientError class AuthorizationError < Error
end end
##
# A 5xx class HTTP error occurred. # A 5xx class HTTP error occurred.
class ServerError < TransmissionError class ServerError < Error
end
##
# An exception that is raised if an ID token could not be validated.
class InvalidIDTokenError < StandardError
end end
# Error class for problems in batch requests. # Error class for problems in batch requests.
class BatchError < StandardError class BatchError < Error
end end
end end
end end

View File

@ -0,0 +1,70 @@
# Copyright 2015 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 'google/apis/discovery_v1'
require 'google/apis/generator/annotator'
require 'google/apis/generator/model'
require 'google/apis/generator/template'
require 'active_support/inflector'
require 'yaml'
module Google
module Apis
# Generates ruby classes for APIs from discovery documents
# @private
class Generator
Discovery = Google::Apis::DiscoveryV1
# Load templates
def initialize(api_names: nil)
@names = Google::Apis::Generator::Names.new(api_names || File.join(Google::Apis::ROOT, 'api_names.yaml'))
@module_template = Template.load('module.rb')
@service_template = Template.load('service.rb')
@classes_template = Template.load('classes.rb')
@representations_template = Template.load('representations.rb')
end
# Generates ruby source for an API
#
# @param [String] json
# API Description, as JSON text
# @return [Hash<String,String>]
# Hash of generated files keyed by path
def render(json)
api = parse_description(json)
Annotator.process(api, @names)
base_path = ActiveSupport::Inflector.underscore(api.qualified_name)
context = {
'api' => api
}
files = {}
files[base_path + '.rb'] = @module_template.render(context)
files[File.join(base_path, 'service.rb')] = @service_template.render(context)
files[File.join(base_path, 'classes.rb')] = @classes_template.render(context)
files[File.join(base_path, 'representations.rb')] = @representations_template.render(context)
files
end
# Dump mapping of API names
# @return [String] Mapping of paths to ruby names in YAML format
def dump_api_names
@names.dump
end
def parse_description(json)
Discovery::RestDescription::Representation.new(Discovery::RestDescription.new).from_json(json)
end
end
end
end

View File

@ -0,0 +1,271 @@
# Copyright 2015 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 'logger'
require 'erb'
require 'yaml'
require 'multi_json'
require 'active_support/inflector'
require 'google/apis/core/logging'
require 'google/apis/generator/template'
require 'google/apis/generator/model'
require 'google/apis/generator/helpers'
require 'addressable/uri'
module Google
module Apis
# @private
class Generator
# Helper for picking names for methods, properties, types, etc. Performs various normaliations
# as well as allows for overriding individual names from a configuration file for cases
# where algorithmic approaches produce poor APIs.
class Names
include Google::Apis::Core::Logging
include NameHelpers
def initialize(file_path = nil)
if file_path
logger.info { sprintf('Loading API names from %s', file_path) }
@names = YAML.load(File.read(file_path)) || {}
else
@names = {}
end
@path = []
end
def with_path(path)
@path.push(path)
begin
yield
ensure
@path.pop
end
end
def infer_parameter_name
pick_name(normalize_param_name(@path.last))
end
# Determine the ruby method name to generate for a given method in discovery.
# @param [Google::Apis::DiscoveryV1::RestMethod] method
# Fragment of the discovery doc describing the method
def infer_method_name(method)
pick_name(infer_method_name_for_rpc(method) || infer_method_name_from_id(method))
end
def infer_property_name
pick_name(normalize_property_name(@path.last))
end
def pick_name(alt_name)
preferred_name = @names[key]
if preferred_name && preferred_name == alt_name
logger.warn { sprintf("Unnecessary name override '%s': %s", key, alt_name) }
elsif preferred_name.nil?
preferred_name = @names[key] = alt_name
end
preferred_name
end
def []=(key, value)
@names[key] = value
end
def dump
YAML.dump(@names)
end
def key
@path.reduce('') { |a, e| a + '/' + e }
end
private
# For RPC style methods, pick a name based off the request objects.
# @param [Google::Apis::DiscoveryV1::RestMethod] method
# Fragment of the discovery doc describing the method
def infer_method_name_for_rpc(method)
return nil if method.request.nil?
verb = ActiveSupport::Inflector.underscore(method.id.split('.').last)
match = method.request._ref.match(/(.*)(?i:request)/)
return nil if match.nil?
name = ActiveSupport::Inflector.underscore(match[1])
return nil unless name == verb || name.start_with?(verb + '_')
name
end
# For REST style methods, build a method name from the verb/resource(s) in the method
# id. IDs are in the form <api>.<resource>.<verb>
# @param [Google::Apis::DiscoveryV1::RestMethod] method
# Fragment of the discovery doc describing the method
def infer_method_name_from_id(method)
parts = method.id.split('.')
parts.shift
verb = parts.pop
return ActiveSupport::Inflector.underscore(verb) if parts.empty?
resource_name = parts.pop
method_name = verb + '_'
method_name += parts.map { |p| ActiveSupport::Inflector.singularize(p) }.join('_') + '_' unless parts.empty?
if pluralize_method?(verb)
method_name += ActiveSupport::Inflector.pluralize(resource_name)
else
method_name += ActiveSupport::Inflector.singularize(resource_name)
end
ActiveSupport::Inflector.underscore(method_name)
end
end
# Modifies an API description to support ruby code generation. Primarily does:
# - Ensure all names follow appopriate ruby conventions
# - Maps types to ruby types, classes, and resolves $refs
# - Attempts to simplify names where possible to make APIs more sensible
class Annotator
include NameHelpers
include Google::Apis::Core::Logging
# Don't expose these in the API directly.
PARAMETER_BLACKLIST = %w(alt access_token bearer_token oauth_token pp prettyPrint
$.xgafv callback upload_protocol uploadType)
# Prepare the API for the templates.
# @param [Google::Apis::DiscoveryV1::RestDescription] description
# API Description
def self.process(description, api_names = nil)
Annotator.new(description, api_names).annotate_api
end
# @param [Google::Apis::DiscoveryV1::RestDescription] description
# API Description
# @param [Google::Api::Generator::Names] api_names
# Name helper instanace
def initialize(description, api_names = nil)
api_names = Names.new if api_names.nil?
@names = api_names
@rest_description = description
@registered_types = []
@deferred_types = []
@strip_prefixes = []
@all_methods = {}
@path = []
end
def annotate_api
@names.with_path(@rest_description.id) do
@strip_prefixes << @rest_description.name
if @rest_description.auth
@rest_description.auth.oauth2.scopes.each do |key, value|
value.constant = constantize_scope(key)
end
end
@rest_description.parameters.reject! { |k, _v| PARAMETER_BLACKLIST.include?(k) }
annotate_parameters(@rest_description.parameters)
annotate_resource(@rest_description.name, @rest_description)
@rest_description.schemas.each do |k, v|
annotate_type(k, v, @rest_description)
end unless @rest_description.schemas.nil?
end
resolve_type_references
resolve_variants
end
def annotate_type(name, type, parent)
@names.with_path(name) do
type.name = name
type.path = @names.key
type.generated_name = @names.infer_property_name
if type.type == 'object'
type.generated_class_name = ActiveSupport::Inflector.camelize(type.generated_name)
@registered_types << type
end
type.parent = parent
@deferred_types << type if type._ref
type.properties.each do |k, v|
annotate_type(k, v, type)
end unless type.properties.nil?
if type.additional_properties
type.type = 'hash'
annotate_type(ActiveSupport::Inflector.singularize(type.generated_name), type.additional_properties,
parent)
end
annotate_type(ActiveSupport::Inflector.singularize(type.generated_name), type.items, parent) if type.items
end
end
def annotate_resource(name, resource, parent_resource = nil)
@strip_prefixes << name
resource.parent = parent_resource unless parent_resource.nil?
resource.api_methods.each do |_k, v|
annotate_method(v, resource)
end unless resource.api_methods.nil?
resource.resources.each do |k, v|
annotate_resource(k, v, resource)
end unless resource.resources.nil?
end
def annotate_method(method, parent_resource = nil)
@names.with_path(method.id) do
method.parent = parent_resource
method.generated_name = @names.infer_method_name(method)
check_duplicate_method(method)
annotate_parameters(method.parameters)
end
end
def annotate_parameters(parameters)
parameters.each do |key, value|
@names.with_path(key) do
value.name = key
value.generated_name = @names.infer_parameter_name
@deferred_types << value if value._ref
end
end unless parameters.nil?
end
def resolve_type_references
@deferred_types.each do |type|
if type._ref
ref = @rest_description.schemas[type._ref]
ivars = ref.instance_variables - [:@name, :@generated_name]
(ivars).each do |var|
type.instance_variable_set(var, ref.instance_variable_get(var))
end
end
end
end
def resolve_variants
@deferred_types.each do |type|
if type.variant
type.variant.map.each do |v|
ref = @rest_description.schemas[v._ref]
ref.base_ref = type
ref.discriminant = type.variant.discriminant
ref.discriminant_value = v.type_value
end
end
end
end
def check_duplicate_method(m)
if @all_methods.include?(m.generated_name)
logger.error { sprintf('Duplicate method %s generated', m.generated_name) }
fail 'Duplicate name generated'
end
@all_methods[m.generated_name] = m
end
end
end
end
end

View File

@ -0,0 +1,74 @@
module Google
module Apis
# @private
class Generator
# Methods for validating & normalizing symbols
module NameHelpers
KEYWORDS = %w(__ENCODING__ def in self __LINE__ defined? module super __FILE__ do next then BEGIN
else nil true END elsif not undef alias end or unless and ensure redo until begin
false rescue when break for retry while case if return yield class)
PLURAL_METHODS = %w(list search)
# Check to see if the method name should be plauralized
# @return [Boolean]
def pluralize_method?(method_name)
PLURAL_METHODS.include?(method_name)
end
# Check to see if the method is either a keyword or built-in method on object
# @return [Boolean]
def reserved?(name)
keyword?(name) || object_method?(name)
end
# Check to see if the name is a ruby keyword
# @return [Boolean]
def keyword?(name)
KEYWORDS.include?(name)
end
# Check to see if the method already exists on ruby objects
# @return [Boolean]
def object_method?(name)
Object.new.respond_to?(name)
end
# Convert a parameter name to ruby conventions
# @param [String] name
# @return [String] updated param name
def normalize_param_name(name)
name = ActiveSupport::Inflector.underscore(name.gsub(/\W/, '_'))
if reserved?(name)
logger.warn { sprintf('Found reserved keyword \'%1$s\'', name) }
name += '_'
logger.warn { sprintf('Changed to \'%1$s\'', name) }
end
name
end
# Convert a property name to ruby conventions
# @param [String] name
# @return [String]
def normalize_property_name(name)
name = ActiveSupport::Inflector.underscore(name.gsub(/\W/, '_'))
if object_method?(name)
logger.warn { sprintf('Found reserved property \'%1$s\'', name) }
name += '_prop'
logger.warn { sprintf('Changed to \'%1$s\'', name) }
end
name
end
# Converts a scope string into a ruby constant
# @param [String] url
# Url to convert
# @return [String]
def constantize_scope(url)
scope = Addressable::URI.parse(url).path[1..-1].upcase.gsub(/\W/, '_')
scope = 'AUTH_SCOPE' if scope.nil? || scope.empty?
scope
end
end
end
end
end

View File

@ -0,0 +1,130 @@
# Copyright 2015 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 'active_support/inflector'
require 'google/apis/discovery_v1'
# Extend the discovery API classes with additional data needed to make
# code generation produce better results
module Google
module Apis
module DiscoveryV1
TYPE_MAP = {
'string' => 'String',
'boolean' => 'Boolean',
'number' => 'Float',
'integer' => 'Fixnum',
'any' => 'Object'
}
class JsonSchema
attr_accessor :name
attr_accessor :generated_name
attr_accessor :generated_class_name
attr_accessor :base_ref
attr_accessor :parent
attr_accessor :discriminant
attr_accessor :discriminant_value
attr_accessor :path
def properties
@properties ||= {}
end
def qualified_name
parent.qualified_name + '::' + generated_class_name
end
def generated_type
case type
when 'string', 'boolean', 'number', 'integer', 'any'
return 'DateTime' if format == 'date-time'
return 'Date' if format == 'date'
return TYPE_MAP[type]
when 'array'
return sprintf('Array<%s>', items.generated_type)
when 'hash'
return sprintf('Hash<String,%s>', additional_properties.generated_type)
when 'object'
return qualified_name
end
end
end
class RestMethod
attr_accessor :generated_name
attr_accessor :parent
def path_parameters
return [] if parameter_order.nil? || parameters.nil?
parameter_order.map { |name| parameters[name] }.select { |param| param.location == 'path' }
end
def query_parameters
return [] if parameters.nil?
parameters.values.select { |param| param.location == 'query' }
end
end
class RestResource
attr_accessor :parent
def all_methods
m = []
m << api_methods.values unless api_methods.nil?
m << resources.map { |_k, r| r.all_methods } unless resources.nil?
m.flatten
end
end
class RestDescription
def version
ActiveSupport::Inflector.camelize(@version.gsub(/\W/, '-')).gsub(/-/, '_')
end
def name
ActiveSupport::Inflector.camelize(@name)
end
def module_name
name + version
end
def qualified_name
sprintf('Google::Apis::%s', module_name)
end
def service_name
class_name = (canonical_name || name).gsub(/\W/, '')
ActiveSupport::Inflector.camelize(sprintf('%sService', class_name))
end
def all_methods
m = []
m << api_methods.values unless api_methods.nil?
m << resources.map { |_k, r| r.all_methods } unless resources.nil?
m.flatten
end
class Auth
class Oauth2
class Scope
attr_accessor :constant
end
end
end
end
end
end
end

View File

@ -0,0 +1,124 @@
# Copyright 2015 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 'active_support/inflector'
require 'erb'
require 'ostruct'
module Google
module Apis
# @private
class Generator
# Directory containing ERB templates
TEMPLATE_DIR = File.expand_path('../templates', __FILE__)
# Helpers used in ERB templates
module TemplateHelpers
# Get the include path for a ruby module/class
#
# @param [String] module_name
# Fully qualified module/class name
# @return [String]
# Path to file
def to_path(module_name)
ActiveSupport::Inflector.underscore(module_name)
end
# Render a block comment
#
# @param [String] str
# Comment string
# @param [Fixnum] spaces_before
# Number of spaces to indent the comment hash
# @param [Fixnum] spaces_after
# Number of spaces to indent after the comment hash for subsequent lines
# @return [String] formatted comment
def block_comment(str, spaces_before = 0, spaces_after = 0)
return '' if str.nil?
pre = ' ' * spaces_before
post = ' ' * spaces_after
lines = str.gsub(/([{}])/, '`').scan(/.{1,78}(?:\W|$)/).map(&:strip)
lines.join("\n" + pre + '#' + post)
end
# Indent a block of text
#
# @param [String] str
# Content to indent
# @param [Fixnum] spaces
# Number of spaces to indent
# @return [String] formatted content
def indent(str, spaces)
pre = ' ' * spaces
str = pre + str.split(/\n/).join("\n" + pre) + "\n"
return str unless str.strip.empty?
nil
end
# Include a partial inside a template.
#
# @private
# @param [String] partial
# Name of the template
# @param [Hash] context
# Context used to render
# @return [String] rendered content
def include(partial, context)
template = Template.new(sprintf('_%s.tmpl', partial))
template.render(context)
end
end
# Holds local vars/helpers for template rendering
class Context < OpenStruct
include TemplateHelpers
# Get the context for ERB evaluation
# @return [Binding]
def to_binding
binding
end
end
# ERB template for the code generator
class Template
# Loads a template from the template dir. Automatically
# appends the .tmpl suffix
#
# @param [String] template_name
# Name of the template file
def self.load(template_name)
Template.new(sprintf('%s.tmpl', template_name))
end
# @param [String] template_name
# Name of the template file
def initialize(template_name)
file = File.join(TEMPLATE_DIR, template_name)
@erb = ERB.new(File.read(file), nil, '-')
end
# Render the template
#
# @param [Hash] context
# Variables to set when rendering the template
# @return [String] rendered template
def render(context)
ctx = Context.new(context)
@erb.result(ctx.to_binding)
end
end
end
end
end

View File

@ -0,0 +1,40 @@
<% if cls.type == 'object' -%>
# <%= block_comment(cls.description, 0, 1) %>
class <%= cls.generated_class_name %><% if cls.base_ref %> < <%= cls.base_ref.generated_type %><% end %>
include Google::Apis::Core::Hashable
<% for property in cls.properties.values -%>
# <%= block_comment(property.description, 2, 1) %>
# Corresponds to the JSON property `<%= property.name %>`
# @return [<%= property.generated_type %>]
attr_accessor :<%= property.generated_name %>
<% if property.type == 'boolean' -%>
alias_method :<%= property.generated_name %>?, :<%= property.generated_name %>
<% end -%>
<% end -%>
def initialize(**args)
<% if cls.discriminant -%>
@<%= cls.properties[cls.discriminant].generated_name %> = '<%= cls.discriminant_value %>'
<% end -%>
update!(**args)
end
# Update properties of this object
def update!(**args)
<% for property in cls.properties.values -%>
@<%= property.generated_name %> = args[:<%= property.generated_name %>] unless args[:<%= property.generated_name %>].nil?
<% end -%>
end
<% for child_class in cls.properties.values -%>
<% if child_class._ref.nil? -%>
<%= indent(include('class', :cls => child_class, :api => api), 2) -%>
<% end -%>
<% end -%>
end
<% elsif cls.items && cls.items._ref.nil? -%>
<%= include('class', :cls => cls.items, :api => api) -%>
<% elsif cls.additional_properties && cls.additional_properties._ref.nil? -%>
<%= include('class', :cls => cls.additional_properties, :api => api) -%>
<% end -%>

View File

@ -0,0 +1,90 @@
# <%= block_comment(api_method.description, 0, 1) %>
<% for param in api_method.path_parameters -%>
# @param [<% if param.repeated? %>Array<<%= param.generated_type %>>, <% end %><%= param.generated_type %>] <%= param.generated_name %>
<% if param.description -%>
# <%= block_comment(param.description, 0, 3) %>
<% end -%>
<% end -%>
<% if api_method.request -%>
# @param [<%= api.schemas[api_method.request._ref].generated_type %>] <%= api.schemas[api_method.request._ref].generated_name %>_object
<% end -%>
<% for param in api_method.query_parameters -%>
# @param [<% if param.repeated? %>Array<<%= param.generated_type %>>, <% end %><%= param.generated_type %>] <%= param.generated_name %>
<% if param.description -%>
# <%= block_comment(param.description, 0, 3) %>
<% end -%>
<% end -%>
<% for param in api.parameters.values.reject {|p| p.name == 'key'} -%>
# @param [<% if param.repeated? %>Array<<%= param.generated_type %>>, <% end %><%= param.generated_type %>] <%= param.generated_name %>
<% if param.description -%>
# <%= block_comment(param.description, 0, 3) %>
<% end -%>
<% end -%>
<% if api_method.supports_media_upload? -%>
# @param [IO, String] upload_source
# IO stream or filename containing content to upload
# @param [String] content_type
# Content type of the uploaded content.
<% elsif api_method.supports_media_download? -%>
# @param [IO, String] download_dest
# IO stream or filename to receive content download
<% end -%>
# @param [Google::Apis::RequestOptions] options
# Request-specific options
#
# @yield [result, err] Result & error if block supplied
<% if api_method.response -%>
# @yieldparam result [<%= api.schemas[api_method.response._ref].generated_type %>] parsed result object
# @yieldparam err [StandardError] error object if request failed
#
# @return [<%= api.schemas[api_method.response._ref].generated_type %>]
<% else -%>
# @yieldparam result [NilClass] No result returned for this method
# @yieldparam err [StandardError] error object if request failed
#
# @return [void]
<% end -%>
#
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
# @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
# @raise [Google::Apis::AuthorizationError] Authorization is required
def <%= api_method.generated_name %>(<% for param in api_method.path_parameters %><%= param.generated_name %>, <% end %><% if api_method.request %><%= api.schemas[api_method.request._ref].generated_name %>_object = nil, <% end %><% for param in api_method.query_parameters %><%= param.generated_name %>: nil, <% end %><% for param in api.parameters.values.reject {|p| p.name == 'key'} %><%= param.generated_name %>: nil, <% end %><% if api_method.supports_media_upload? %>upload_source: nil, content_type: nil, <% elsif api_method.supports_media_download? %>download_dest: nil, <% end %>options: nil, &block)
path = '<%= api_method.path %>'
<% if api_method.supports_media_upload? -%>
if upload_source.nil?
command = make_simple_command(:<%= api_method.http_method.downcase %>, path, options)
else
command = make_upload_command(:<%= api_method.http_method.downcase %>, path, options)
command.upload_source = upload_source
command.upload_content_type = content_type
end
<% elsif api_method.supports_media_download? -%>
if download_dest.nil?
command = make_simple_command(:<%= api_method.http_method.downcase %>, path, options)
else
command = make_download_command(:<%= api_method.http_method.downcase %>, path, options)
command.download_dest = download_dest
end
<% else -%>
command = make_simple_command(:<%= api_method.http_method.downcase %>, path, options)
<% end -%>
<% if api_method.request -%>
command.request_representation = <%= api.schemas[api_method.request._ref].generated_type %>::Representation
command.request_object = <%= api.schemas[api_method.request._ref].generated_name %>_object
<% end -%>
<% if api_method.response -%>
command.response_representation = <%= api.schemas[api_method.response._ref].generated_type %>::Representation
command.response_class = <%= api.schemas[api_method.response._ref].generated_type %>
<% end -%>
<% for param in api_method.path_parameters -%>
command.params['<%= param.name %>'] = <%= param.generated_name %> unless <%= param.generated_name %>.nil?
<% end -%>
<% for param in api_method.query_parameters -%>
command.query['<%= param.name %>'] = <%= param.generated_name %> unless <%= param.generated_name %>.nil?
<% end -%>
<% for param in api.parameters.values.reject {|p| p.name == 'key'} -%>
command.query['<%= param.name %>'] = <%= param.generated_name %> unless <%= param.generated_name %>.nil?
<% end -%>
execute_or_queue_command(command, &block)
end

View File

@ -0,0 +1,51 @@
<% if cls.type == 'object' -%>
# @private
class <%= cls.generated_class_name %>
class Representation < Google::Apis::Core::JsonRepresentation
<% if api.features && api.features.include?('dataWrapper') -%>
self.representation_wrap = lambda { |args| :data if args[:unwrap] == <%= cls.generated_type %> }
<% end -%>
<% if cls.variant -%>
def from_hash(hash, *args)
case hash['<%= cls.variant.discriminant %>']
<% for v in cls.variant.map -%>
<% ref = api.schemas[v._ref] %>
when '<%= v.type_value %>'
<%= ref.generated_type %>::Representation.new(<%= ref.generated_type %>.new).from_hash(hash, *args)
<% end -%>
end
end
def to_hash(*args)
case represented
<% for v in cls.variant.map -%>
<% ref = api.schemas[v._ref] %>
when <%= ref.generated_type %>
<%= ref.generated_type %>::Representation.new(represented).to_hash(*args)
<% end -%>
end
end
<% else -%>
<% for property in cls.properties.values -%>
<% if property.type == 'hash' -%>
hash :<%= property.generated_name %>, as: '<%= property.name %>'<%= include('representation_type', :lead => ', ', :type => property.additional_properties, :api => api) %>
<% elsif property.type == 'array' -%>
collection :<%= property.generated_name %>, as: '<%= property.name %>'<%= include('representation_type', :lead => ', ', :type => property.items, :api => api) %>
<% else -%>
property :<%= property.generated_name %>,<% if property.format == 'byte' %> :base64 => true,<%end%> as: '<%= property.name %>'<%= include('representation_type', :lead => ', ', :type => property, :api => api) %>
<% end -%>
<% end -%>
<% end -%>
end
<% for child_class in cls.properties.values -%>
<% if child_class._ref.nil? -%>
<%= indent(include('representation', :cls => child_class, :api => api), 2) -%>
<% end -%>
<% end -%>
end
<% elsif cls.items && cls.items._ref.nil? -%>
<%= include('representation', :cls => cls.items, :api => api) -%>
<% elsif cls.additional_properties && cls.additional_properties._ref.nil? -%>
<%= include('representation', :cls => cls.additional_properties, :api => api) -%>
<% end -%>

View File

@ -0,0 +1,15 @@
<% if cls.type == 'object' -%>
class <%= cls.generated_class_name %>
class Representation < Google::Apis::Core::JsonRepresentation; end
<% for child_class in cls.properties.values -%>
<% if child_class._ref.nil? -%>
<%= indent(include('representation_stub', :cls => child_class), 2) -%>
<% end -%>
<% end -%>
end
<% elsif cls.items && cls.items._ref.nil? -%>
<%= include('representation_stub', :cls => cls.items, :api => api) -%>
<% elsif cls.additional_properties && cls.additional_properties._ref.nil? -%>
<%= include('representation_stub', :cls => cls.additional_properties, :api => api) -%>
<% end -%>

View File

@ -0,0 +1,10 @@
<% if type.type == 'array' -%>
<%= lead %>:class => Array do
include Representable::JSON::Collection
items<%= include('representation_type', :lead => ' ' , :type => type.items, :api => api) %>
end
<% elsif ['date', 'date-time'].include?(type.format) -%>
<%= lead %>type: <%= type.generated_type %>
<% elsif type.type == 'object' -%>
<%= lead %>class: <%= type.generated_type %>, decorator: <%= type.generated_type %>::Representation
<% end -%>

View File

@ -1,4 +1,4 @@
# Copyright 2010 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,16 +12,18 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'google/api_client/request' require 'date'
require 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
module Google module Google
class APIClient module Apis
## module <%= api.module_name %>
# Subclass of Request for backwards compatibility with pre-0.5.0 versions of the library <% for cls in api.schemas.values.partition(&:variant).flatten -%>
# <%= indent(include('class', :cls => cls, :api => api), 6) -%>
# @deprecated <% end -%>
# use {Google::APIClient::Request} instead
class Reference < Request
end end
end end
end end

View File

@ -0,0 +1,40 @@
# Copyright 2015 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 '<%= to_path(api.qualified_name) %>/service.rb'
require '<%= to_path(api.qualified_name) %>/classes.rb'
require '<%= to_path(api.qualified_name) %>/representations.rb'
module Google
module Apis
# <%= api.title %>
#
# <%= block_comment(api.description, 4, 1) %>
#
<% if api.documentation_link -%>
# @see <%= api.documentation_link %>
<% end -%>
module <%= api.module_name %>
VERSION = '<%= api.version %>'
REVISION = '<%= api.revision %>'
<% if api.auth && api.auth.oauth2 -%>
<% for scope_string, scope in api.auth.oauth2.scopes -%>
# <%= scope.description %>
<%= scope.constant %> = '<%= scope_string %>'
<% end -%>
<% end -%>
end
end
end

View File

@ -1,4 +1,4 @@
# Copyright 2012 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,18 +12,21 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'spec_helper' require 'date'
require 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
require 'google/api_client' module Google
module Apis
RSpec.describe Google::APIClient::Request do module <%= api.module_name %>
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) <% for cls in api.schemas.values.partition(&:variant).flatten -%>
<%= indent(include('representation_stub', :cls => cls), 6) -%>
it 'should normalize parameter names to strings' do <% end -%>
request = Google::APIClient::Request.new(:uri => 'https://www.google.com', :parameters => { <% for cls in api.schemas.values.partition(&:variant).flatten -%>
:a => '1', 'b' => '2' <%= indent(include('representation', :cls => cls, :api => api), 6) -%>
}) <% end -%>
expect(request.parameters['a']).to eq('1') end
expect(request.parameters['b']).to eq('2')
end end
end end

View File

@ -0,0 +1,60 @@
# Copyright 2015 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 'google/apis/core/base_service'
require 'google/apis/core/json_representation'
require 'google/apis/core/hashable'
require 'google/apis/errors'
module Google
module Apis
module <%= api.module_name %>
# <%= api.title %>
#
# <%= block_comment(api.description, 6, 2) %>
#
# @example
# require '<%= to_path(api.qualified_name) %>'
#
# <%= api.name %> = <%= api.qualified_name %> # Alias the module
# service = <%= api.name %>::<%= api.service_name %>.new
#
<% if api.documentation_link -%>
# @see <%= api.documentation_link %>
<% end -%>
class <%= api.service_name %> < Google::Apis::Core::BaseService
<% for param in api.parameters.values.reject {|p| p.name == 'fields'} -%>
# @return [<%= param.generated_type %>]
# <%= block_comment(param.description, 8, 2) %>
attr_accessor :<%= param.generated_name %>
<% end -%>
def initialize
super('<%= api.root_url %>', '<%= api.service_path %>')
end
<% for api_method in api.all_methods -%>
<%= indent(include('method', :api_method => api_method, :api => api), 8) -%>
<% end -%>
protected
def apply_command_defaults(command)
<% for param in api.parameters.values.reject {|p| p.name == 'fields'} -%>
command.query['<%= param.name %>'] = <%= param.generated_name %> unless <%= param.generated_name %>.nil?
<% end -%>
end
end
end
end
end

View File

@ -0,0 +1,79 @@
# Copyright 2015 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.
module Google
module Apis
# General options for API requests
ClientOptions = Struct.new(
:application_name,
:application_version,
:proxy_url)
RequestOptions = Struct.new(
:authorization,
:retries,
:header,
:timeout_sec)
# General client options
class ClientOptions
# @!attribute [rw] application_name
# @return [String] Name of the application, for identification in the User-Agent header
# @!attribute [rw] application_version
# @return [String] Version of the application, for identification in the User-Agent header
# @!attribute [rw] proxy_url
# @return [String] URL of a proxy server
# Get the default options
# @return [Google::Apis::ClientOptions]
def self.default
@options ||= ClientOptions.new
end
end
# Request options
class RequestOptions
# @!attribute [rw] credentials
# @return [Signet::OAuth2::Client, #apply(Hash)] OAuth2 credentials
# @!attribute [rw] retries
# @return [Fixnum] Number of times to retry requests on server error
# @!attribute [rw] timeout_sec
# @return [Fixnum] How long, in seconds, before requests time out
# @!attribute [rw] header
# @return [Hash<String,String] Additional HTTP headers to include in requests
# Get the default options
# @return [Google::Apis::RequestOptions]
def self.default
@options ||= RequestOptions.new
end
def merge(options)
return self if options.nil?
new_options = dup
%w(authorization retries timeout_sec).each do |opt|
opt = opt.to_sym
new_options[opt] = options[opt] unless options[opt].nil?
end
new_options
end
end
ClientOptions.default.application_name = 'unknown'
ClientOptions.default.application_version = '0.0.0'
RequestOptions.default.retries = 0
end
end

View File

@ -0,0 +1,39 @@
# Copyright 2015 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.
module Google
module Apis
# Client library version
VERSION = '0.9.pre1'
# Current operating system
# @private
OS_VERSION = begin
if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
elsif RUBY_PLATFORM =~ /darwin/i
"Mac OS X/#{`sw_vers -productVersion`}"
elsif RUBY_PLATFORM == 'java'
require 'java'
name = java.lang.System.getProperty('os.name')
version = java.lang.System.getProperty('os.version')
"#{name}/#{version}"
else
`uname -sr`.sub(' ', '/')
end
rescue
RUBY_PLATFORM
end
end
end

View File

@ -1,34 +0,0 @@
require "rubygems/package_task"
namespace :gem do
desc "Build the gem"
task :build do
system "gem build signet.gemspec"
end
desc "Install the gem"
task :install => ["clobber", "gem:package"] do
sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}"
end
desc "Uninstall the gem"
task :uninstall do
installed_list = Gem.source_index.find_name(PKG_NAME)
if installed_list &&
(installed_list.collect { |s| s.version.to_s}.include?(PKG_VERSION))
sh(
"#{SUDO} gem uninstall --version '#{PKG_VERSION}' " +
"--ignore-dependencies --executables #{PKG_NAME}"
)
end
end
desc "Reinstall the gem"
task :reinstall => [:uninstall, :install]
end
desc "Alias to gem:package"
task "gem" => "gem:package"
task "clobber" => ["gem:clobber_package"]

View File

@ -1,45 +0,0 @@
namespace :git do
namespace :tag do
desc 'List tags from the Git repository'
task :list do
tags = `git tag -l`
tags.gsub!("\r", '')
tags = tags.split("\n").sort {|a, b| b <=> a }
puts tags.join("\n")
end
desc 'Create a new tag in the Git repository'
task :create do
changelog = File.open('CHANGELOG.md', 'r') { |file| file.read }
puts '-' * 80
puts changelog
puts '-' * 80
puts
v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
abort "Versions don't match #{v} vs #{PKG_VERSION}" if v != PKG_VERSION
git_status = `git status`
if git_status !~ /nothing to commit \(working directory clean\)/
abort "Working directory isn't clean."
end
tag = "#{PKG_NAME}-#{PKG_VERSION}"
msg = "Release #{PKG_NAME}-#{PKG_VERSION}"
existing_tags = `git tag -l #{PKG_NAME}-*`.split('\n')
if existing_tags.include?(tag)
warn('Tag already exists, deleting...')
unless system "git tag -d #{tag}"
abort 'Tag deletion failed.'
end
end
puts "Creating git tag '#{tag}'..."
unless system "git tag -a -m \"#{msg}\" #{tag}"
abort 'Tag creation failed.'
end
end
end
end
task 'gem:release' => 'git:tag:create'

View File

@ -1,7 +1,7 @@
namespace :metrics do namespace :metrics do
task :lines do task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
for file_name in FileList['lib/**/*.rb'] for file_name in FileList['lib/**/*.rb', 'bin/generate-api']
f = File.open(file_name) f = File.open(file_name)
while line = f.gets while line = f.gets
lines += 1 lines += 1

10
rakelib/rubocop.rake Normal file
View File

@ -0,0 +1,10 @@
require 'rubocop/rake_task'
desc 'Run RuboCop on the lib directory'
RuboCop::RakeTask.new(:rubocop) do |task|
task.patterns = ['lib/**/*.rb']
# only show the files with failures
task.formatters = ['progress']
# don't abort rake on failure
task.fail_on_error = false
end

View File

@ -1,21 +1,10 @@
require 'rake/clean' require 'rake/clean'
require 'rspec/core/rake_task' require 'rspec/core/rake_task'
CLOBBER.include('coverage', 'specdoc') CLOBBER.include('coverage')
namespace :spec do namespace :spec do
RSpec::Core::RakeTask.new(:all) do |t| RSpec::Core::RakeTask.new(:all)
t.pattern = FileList['spec/**/*_spec.rb']
t.rspec_opts = ['--color', '--format', 'documentation']
end
desc 'Generate HTML Specdocs for all specs.'
RSpec::Core::RakeTask.new(:specdoc) do |t|
specdoc_path = File.expand_path('../../specdoc', __FILE__)
t.rspec_opts = %W( --format html --out #{File.join(specdoc_path, 'index.html')} )
t.fail_on_error = false
end
end end
desc 'Alias to spec:all' desc 'Alias to spec:all'

View File

@ -1,82 +0,0 @@
require 'rake'
require 'rake/clean'
CLOBBER.include('wiki')
CACHE_PREFIX =
"http://www.gmodules.com/gadgets/proxy/container=default&debug=0&nocache=0/"
namespace :wiki do
desc 'Autogenerate wiki pages'
task :supported_apis do
output = <<-WIKI
#summary The list of supported APIs
The Google API Client for Ruby is a small flexible client library for accessing
the following Google APIs.
WIKI
preferred_apis = {}
require 'google/api_client'
client = Google::APIClient.new
for api in client.discovered_apis
if !preferred_apis.has_key?(api.name)
preferred_apis[api.name] = api
elsif api.preferred
preferred_apis[api.name] = api
end
end
for api_name, api in preferred_apis
if api.documentation.to_s != "" && api.title != ""
output += (
"||#{CACHE_PREFIX}#{api['icons']['x16']}||" +
"[#{api.documentation} #{api.title}]||" +
"#{api.description}||\n"
)
end
end
output.gsub!(/-32\./, "-16.")
wiki_path = File.expand_path(
File.join(File.dirname(__FILE__), '../wiki/'))
Dir.mkdir(wiki_path) unless File.exists?(wiki_path)
File.open(File.join(wiki_path, 'SupportedAPIs.wiki'), 'w') do |file|
file.write(output)
end
end
task 'generate' => ['wiki:supported_apis']
end
begin
$LOAD_PATH.unshift(
File.expand_path(File.join(File.dirname(__FILE__), '../yard/lib'))
)
$LOAD_PATH.unshift(File.expand_path('.'))
$LOAD_PATH.uniq!
require 'yard'
require 'yard/rake/wikidoc_task'
namespace :wiki do
desc 'Generate Wiki Documentation with YARD'
YARD::Rake::WikidocTask.new do |yardoc|
yardoc.name = 'reference'
yardoc.options = [
'--verbose',
'--markup', 'markdown',
'-e', 'yard/lib/yard-google-code.rb',
'-p', 'yard/templates',
'-f', 'wiki',
'-o', 'wiki'
]
yardoc.files = [
'lib/**/*.rb', 'ext/**/*.c', '-', 'README.md', 'CHANGELOG.md'
]
end
task 'generate' => ['wiki:reference', 'wiki:supported_apis']
end
rescue LoadError
# If yard isn't available, it's not the end of the world
warn('YARD unavailable. Cannot fully generate wiki.')
end

View File

@ -1,29 +1,11 @@
require 'rake'
require 'rake/clean'
CLOBBER.include('doc', '.yardoc')
CLOBBER.uniq!
begin begin
require 'yard' require 'yard'
require 'yard/rake/yardoc_task' require 'yard/rake/yardoc_task'
namespace :doc do YARD::Rake::YardocTask.new do |t|
desc 'Generate Yardoc documentation' t.files = ['lib/**/*.rb', 'generated/**/*.rb']
YARD::Rake::YardocTask.new do |yardoc| t.options = ['--verbose', '--markup', 'markdown']
yardoc.name = 'yard'
yardoc.options = ['--verbose', '--markup', 'markdown']
yardoc.files = [
'lib/**/*.rb', 'ext/**/*.c', '-',
'README.md', 'CONTRIB.md', 'CHANGELOG.md', 'LICENSE'
]
end end
end
desc 'Alias to doc:yard'
task 'doc' => 'doc:yard'
rescue LoadError rescue LoadError
# If yard isn't available, it's not the end of the world puts "YARD not available"
desc 'Alias to doc:rdoc'
task 'doc' => 'doc:rdoc'
end end

View File

@ -0,0 +1,43 @@
# Copyright 2015 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 'googleauth'
require 'google/apis/calendar_v3'
Calendar = Google::Apis::CalendarV3
calendar = Calendar::CalendarService.new
calendar.authorization = Google::Auth.get_application_default([Calendar::AUTH_CALENDAR])
# Create an event, adding any emails listed in the command line as attendees
event = Calendar::Event.new(summary: 'A sample event',
location: '1600 Amphitheatre Parkway, Mountain View, CA 94045',
attendees: ARGV.map { |email| Calendar::EventAttendee.new(email: email) },
start: Calendar::EventDateTime.new(date_time: DateTime.parse('2015-12-31T20:00:00')),
end: Calendar::EventDateTime.new(date_time: DateTime.parse('2016-01-01T02:00:00')))
event = calendar.insert_event('primary', event, send_notifications: true)
puts "Created event '#{event.summary}' (#{event.id})"
# List upcoming events
events = calendar.list_events('primary', max_results: 10, single_events: true,
order_by: 'startTime', time_min: Time.now.iso8601)
puts "Upcoming events:"
events.items.each do |evt|
start = event.start.date || event.start.date_time
puts "- #{event.summary} (#{start}) (ID: #{event.id})"
end
# Delete the event we created earlier
calendar.delete_event('primary', event.id, send_notifications: true)
puts "Event deleted"

40
samples/drive/drive.rb Normal file
View File

@ -0,0 +1,40 @@
# Copyright 2015 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 'tempfile'
require 'googleauth'
require 'google/apis/drive_v2'
Drive = Google::Apis::DriveV2
drive = Drive::DriveService.new
drive.authorization = Google::Auth.get_application_default([Drive::AUTH_DRIVE])
# Insert a file
file = drive.insert_file({title: 'drive.rb'}, upload_source: 'drive.rb')
puts "Created file #{file.title} (#{file.id})"
# Search for it
files = drive.list_files(q: "title = 'drive.rb'")
puts "Search results:"
files.items.each do |file|
puts "- File: #{file.title} (#{file.id})"
end
# Read it back
tmp = drive.get_file(file.id, download_dest: Tempfile.new('drive'))
# Delete it
drive.delete_file(file.id)
puts "File deleted"

52
samples/pubsub/pubsub.rb Normal file
View File

@ -0,0 +1,52 @@
# Copyright 2015 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 'googleauth'
require 'google/apis/pubsub_v1beta2'
Pubsub = Google::Apis::PubsubV1beta2
pubsub = Pubsub::PubsubService.new
pubsub.authorization = Google::Auth.get_application_default([Pubsub::AUTH_PUBSUB])
project = ARGV[0] || 'YOUR_PROJECT_NAME'
topic = "projects/#{project}/topics/foo"
subscription = "projects/#{project}/subscriptions/bar"
# Create topic & subscription
pubsub.create_topic(topic)
pubsub.create_subscription(subscription, Pubsub::Subscription.new(topic: topic))
# Publish messages
request = Pubsub::PublishRequest.new(messages: [])
request.messages << Pubsub::Message.new(attributes: { "language" => "en" }, data: 'Hello')
request.messages << Pubsub::Message.new(attributes: { "language" => "en" }, data: 'World')
pubsub.publish(topic, request)
# Pull messages
response = pubsub.pull(subscription, Pubsub::PullRequest.new(max_messages: 5))
response.received_messages.each do |received_message|
data = received_message.message.data
puts "Received #{data}"
end
# Acknowledge receipt
ack_ids = response.received_messages.map{ |msg| msg.ack_id }
pubsub.acknowledge(subscription, Pubsub::AcknowledgeRequest.new(ack_ids: ack_ids))
# Delete the subscription & topic
pubsub.delete_subscription(subscription)
pubsub.delete_topic(topic)

View File

@ -1,4 +1,4 @@
# Copyright 2010 Google Inc. # Copyright 2015 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -12,8 +12,17 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
require 'tempfile'
require 'googleauth'
require 'google/apis/translate_v2'
Google::Apis.logger.level = Logger::DEBUG
Translate = Google::Apis::TranslateV2
translate = Translate::TranslateService.new
translate.key = ARGV[0] || 'YOUR_API_KEY'
result = translate.list_translations(source: 'en', target: 'es', q: 'Hello world!')
puts result.translations.first.translated_text
require 'google/api_client/discovery/api'
require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method'
require 'google/api_client/discovery/schema'

79
script/generate Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Usage: script/generate
# Update packaged APIs
DIR=$(dirname $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ))
APIS=(adexchangebuyer:v1.3 \
adexchangeseller:v2.0 \
admin:directory_v1 \
admin:reports_v1 \
adsense:v1.4 \
adsensehost:v4.1 \
analytics:v3 \
androidenterprise:v1 \
androidpublisher:v2 \
appsactivity:v1 \
appstate:v1 \
autoscaler:v1beta2 \
bigquery:v2 \
blogger:v3 \
books:v1 \
calendar:v3 \
civicinfo:v2 \
cloudmonitoring:v2beta2 \
cloudresourcemanager:v1beta1 \
compute:v1 \
container:v1beta1 \
content:v2 \
coordinate:v1 \
customsearch:v1 \
datastore:v1beta2 \
deploymentmanager:v2beta2 \
dfareporting:v2.1 \
discovery:v1 \
dns:v1 \
doubleclickbidmanager:v1 \
doubleclicksearch:v2 \
drive:v2 \
fitness:v1 \
fusiontables:v2 \
games:v1 \
gamesConfiguration:v1configuration \
gamesConfiguration:v1management \
gan:v1beta1 \
genomics:v1beta2 \
gmail:v1 \
groupsmigration:v1 \
groupssettings:v1 \
identitytoolkit:v3 \
licensing:v1 \
logging:v1beta3 \
manager:v1beta2 \
mapsengine:v1 \
mirror:v1 \
oauth2:v2 \
pagespeedonline:v2 \
plus:v1 \
plusDomains:v1 \
prediction:v1.6 \
pubsub:v1beta2 \
qpxExpress:v1 \
replicapool:v1beta2 \
replicapoolupdater:v1beta1 \
reseller:v1 \
resourceviews:v1beta2 \
siteVerification:v1 \
sqladmin:v1beta4 \
storage:v1 \
tagmanager:v1 \
taskqueue:v1beta2 \
tasks:v1 \
translate:v2 \
urlshortener:v1 \
webmasters:v3 \
youtube:v3 \
youtubeAnalytics:v1 \
)
echo 'a' | bundle exec bin/generate-api gen generated --names_out=$DIR/api_names_out.yaml --id=${APIS[*]}

View File

@ -1,4 +1,5 @@
age: script/release #!/usr/bin/env bash
# Usage: script/release
# Build the package, tag a commit, push it to origin, and then release the # Build the package, tag a commit, push it to origin, and then release the
# package publicly. # package publicly.

3
spec/fixtures/files/api_names.yaml vendored Normal file
View File

@ -0,0 +1,3 @@
---
"/test:v1/TestAnotherThing": another_thing

Binary file not shown.

View File

@ -1,33 +0,0 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus posuere urna bibendum diam vulputate fringilla. Fusce elementum fermentum justo id aliquam. Integer vel felis ut arcu elementum lacinia. Duis congue urna eget nisl dapibus tristique molestie turpis sollicitudin. Vivamus in justo quam. Proin condimentum mollis tortor at molestie. Cras luctus, nunc a convallis iaculis, est risus consequat nisi, sit amet sollicitudin metus mi a urna. Aliquam accumsan, massa quis condimentum varius, sapien massa faucibus nibh, a dignissim magna nibh a lacus. Nunc aliquet, nunc ac pulvinar consectetur, sapien lacus hendrerit enim, nec dapibus lorem mi eget risus. Praesent vitae justo eget dolor blandit ullamcorper. Duis id nibh vitae sem aliquam vehicula et ac massa. In neque elit, molestie pulvinar viverra at, vestibulum quis velit.
Mauris sit amet placerat enim. Duis vel tellus ac dui auctor tincidunt id nec augue. Donec ut blandit turpis. Mauris dictum urna id urna vestibulum accumsan. Maecenas sagittis urna vitae erat facilisis gravida. Phasellus tellus augue, commodo ut iaculis vitae, interdum ut dolor. Proin at dictum lorem. Quisque pellentesque neque ante, vitae rutrum elit. Pellentesque sit amet erat orci. Praesent justo diam, tristique eu tempus ut, vestibulum eget dui. Maecenas et elementum justo. Cras a augue a elit porttitor placerat eget ut magna.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam adipiscing tellus in arcu bibendum volutpat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed laoreet faucibus tristique. Duis metus eros, molestie eget dignissim in, imperdiet fermentum nulla. Vestibulum laoreet lorem eu justo vestibulum lobortis. Praesent pharetra leo vel mauris rhoncus commodo sollicitudin ante auctor. Ut sagittis, tortor nec placerat rutrum, neque ipsum cursus nisl, ut lacinia magna risus ac risus. Sed volutpat commodo orci, sodales fermentum dui accumsan eu. Donec egestas ullamcorper elit at condimentum. In euismod sodales posuere. Nullam lacinia tempus molestie. Etiam vitae ullamcorper dui. Fusce congue suscipit arcu, at consectetur diam gravida id. Quisque augue urna, commodo eleifend volutpat vitae, tincidunt ac ligula. Curabitur eget orci nisl, vel placerat ipsum.
Curabitur rutrum euismod nisi, consectetur varius tortor condimentum non. Pellentesque rhoncus nisi eu purus ultricies suscipit. Morbi ante nisi, varius nec molestie bibendum, pharetra quis enim. Proin eget nunc ante. Cras aliquam enim vel nunc laoreet ut facilisis nunc interdum. Fusce libero ipsum, posuere eget blandit quis, bibendum vitae quam. Integer dictum faucibus lacus eget facilisis. Duis adipiscing tortor magna, vel tincidunt risus. In non augue eu nisl sodales cursus vel eget nisi. Maecenas dignissim lectus elementum eros fermentum gravida et eget leo. Aenean quis cursus arcu. Mauris posuere purus non diam mattis vehicula. Integer nec orci velit.
Integer ac justo ac magna adipiscing condimentum vitae tincidunt dui. Morbi augue arcu, blandit nec interdum sit amet, condimentum vel nisl. Nulla vehicula tincidunt laoreet. Aliquam ornare elementum urna, sed vehicula magna porta id. Vestibulum dictum ultrices tortor sit amet tincidunt. Praesent bibendum, metus vel volutpat interdum, nisl nunc cursus libero, vel congue ligula mi et felis. Nulla mollis elementum nulla, in accumsan risus consequat at. Suspendisse potenti. Vestibulum enim lorem, dignissim ut porta vestibulum, porta eget mi. Fusce a elit ac dui sodales gravida. Pellentesque sed elit at dui dapibus mattis a non arcu.
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In nec posuere augue. Praesent non suscipit arcu. Sed nibh risus, lacinia ut molestie vitae, tristique eget turpis. Sed pretium volutpat arcu, non rutrum leo volutpat sed. Maecenas quis neque nisl, sit amet ornare dolor. Nulla pharetra pulvinar tellus sed eleifend. Aliquam eget mattis nulla. Nulla dictum vehicula velit, non facilisis lorem volutpat id. Fusce scelerisque sem vitae purus dapibus lobortis. Mauris ac turpis nec nibh consequat porttitor. Ut sit amet iaculis lorem. Vivamus blandit erat ac odio venenatis fringilla a sit amet ante. Quisque ut urna sed augue laoreet sagittis.
Integer nisl urna, bibendum id lobortis in, tempor non velit. Fusce sed volutpat quam. Suspendisse eu placerat purus. Maecenas quis feugiat lectus. Sed accumsan malesuada dui, a pretium purus facilisis quis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc ac purus id lacus malesuada placerat et in nunc. Ut imperdiet tincidunt est, at consectetur augue egestas hendrerit. Pellentesque eu erat a dui dignissim adipiscing. Integer quis leo non felis placerat eleifend. Fusce luctus mi a lorem mattis eget accumsan libero posuere. Sed pellentesque, odio id pharetra tempus, enim quam placerat metus, auctor aliquam elit mi facilisis quam. Nam at velit et eros rhoncus accumsan.
Donec tellus diam, fringilla ac viverra fringilla, rhoncus sit amet purus. Cras et ligula sed nibh tempor gravida. Aliquam id tempus mauris. Ut convallis quam sed arcu varius eget mattis magna tincidunt. Aliquam et suscipit est. Sed metus augue, tristique sed accumsan eget, euismod et augue. Nam augue sapien, placerat vel facilisis eu, tempor id risus. Aliquam mollis egestas mi. Fusce scelerisque convallis mauris quis blandit. Mauris nec ante id lacus sagittis tincidunt ornare vehicula dui. Curabitur tristique mattis nunc, vel cursus libero viverra feugiat. Suspendisse at sapien velit, a lacinia dolor. Vivamus in est non odio feugiat lacinia sodales ut magna.
Donec interdum ligula id ipsum dapibus consectetur. Pellentesque vitae posuere ligula. Morbi rhoncus bibendum eleifend. Suspendisse fringilla nunc at elit malesuada vitae ullamcorper lorem laoreet. Suspendisse a ante at ipsum iaculis cursus. Duis accumsan ligula quis nibh luctus pretium. Duis ultrices scelerisque dolor, et vulputate lectus commodo ut.
Vestibulum ac tincidunt lorem. Vestibulum lorem massa, dictum a scelerisque ut, convallis vitae eros. Morbi ipsum nisl, lacinia non tempor nec, lobortis id diam. Fusce quis magna nunc. Proin ultricies congue justo sed mattis. Vestibulum sit amet arcu tellus. Quisque ultricies porta massa iaculis vehicula. Vestibulum sollicitudin tempor urna vel sodales. Pellentesque ultricies tellus vel metus porta nec iaculis sapien mollis. Maecenas ullamcorper, metus eget imperdiet sagittis, odio orci dapibus neque, in vulputate nunc nibh non libero. Donec velit quam, lobortis quis tempus a, hendrerit id arcu.
Donec nec ante at tortor dignissim mattis. Curabitur vehicula tincidunt magna id sagittis. Proin euismod dignissim porta. Curabitur non turpis purus, in rutrum nulla. Nam turpis nulla, tincidunt et hendrerit non, posuere nec enim. Curabitur leo enim, lobortis ut placerat id, condimentum nec massa. In bibendum, lectus sit amet molestie commodo, felis massa rutrum nisl, ac fermentum ligula lacus in ipsum.
Pellentesque mi nulla, scelerisque vitae tempus id, consequat a augue. Quisque vel nisi sit amet ipsum faucibus laoreet sed vitae lorem. Praesent nunc tortor, volutpat ac commodo non, pharetra sed neque. Curabitur nec felis at mi blandit aliquet eu ornare justo. Mauris dignissim purus quis nisl porttitor interdum. Aenean id ipsum enim, blandit commodo justo. Quisque facilisis elit quis velit commodo scelerisque lobortis sapien condimentum. Cras sit amet porttitor velit. Praesent nec tempor arcu.
Donec varius mi adipiscing elit semper vel feugiat ipsum dictum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non quam nisl, ac mattis justo. Vestibulum sed massa eget velit tristique auctor ut ac sapien. Curabitur aliquet ligula eget dui ornare at scelerisque mauris faucibus. Vestibulum id mauris metus, sed vestibulum nibh. Nulla egestas dictum blandit. Mauris vitae nibh at dui mollis lobortis. Phasellus sem leo, euismod at fringilla quis, mollis in nibh. Aenean vel lacus et elit pharetra elementum. Aliquam at ligula id sem bibendum volutpat. Pellentesque quis elit a massa dapibus viverra ut et lorem. Donec nulla eros, iaculis nec commodo vel, suscipit sit amet tortor. Integer tempor, elit at viverra imperdiet, velit sapien laoreet nunc, id laoreet ligula risus vel risus. Nullam sed tortor metus.
In nunc orci, tempor vulputate pretium vel, suscipit quis risus. Suspendisse accumsan facilisis felis eget posuere. Donec a faucibus felis. Proin nibh erat, sollicitudin quis vestibulum id, tincidunt quis justo. In sed purus eu nisi dignissim condimentum. Sed mattis dapibus lorem id vulputate. Suspendisse nec elit a augue interdum consequat quis id magna. In eleifend aliquam tempor. In in lacus augue.
Ut euismod sollicitudin lorem, id aliquam magna dictum sed. Nunc fringilla lobortis nisi sed consectetur. Nulla facilisi. Aenean nec lobortis augue. Curabitur ullamcorper dapibus libero, vel pellentesque arcu sollicitudin non. Praesent varius, turpis nec sollicitudin bibendum, elit tortor rhoncus lacus, gravida luctus leo nisi in felis. Ut metus eros, molestie non faucibus vel, condimentum ac elit.
Suspendisse nisl justo, lacinia sit amet interdum nec, tincidunt placerat urna. Suspendisse potenti. In et odio sed purus malesuada cursus sed nec lectus. Cras commodo, orci sit amet hendrerit iaculis, nunc urna facilisis tellus, vel laoreet odio nulla quis nibh. Maecenas ut justo ut lacus posuere sodales. Vestibulum facilisis fringilla diam at volutpat. Proin a hendrerit urna. Aenean placerat pulvinar arcu, sit amet lobortis neque eleifend in. Aenean risus nulla, facilisis ut tincidunt vitae, fringilla at ligula. Praesent eleifend est at sem lacinia auctor. Nulla ornare nunc in erat laoreet blandit.
Suspendisse pharetra leo ac est porta consequat. Nunc sem nibh, gravida vel aliquam a, ornare in tortor. Nulla vel sapien et felis placerat pellentesque id scelerisque nisl. Praesent et posuere.

View File

@ -1,19 +0,0 @@
Bag Attributes
friendlyName: privatekey
localKeyID: 54 69 6D 65 20 31 33 35 31 38 38 38 31 37 38 36 39 36
Key Attributes: <No Attributes>
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDYDyPb3GhyFx5i/wxS/jFsO6wSLys1ehAk6QZoBXGlg7ETVrIJ
HYh9gXQUno4tJiQoaO8wOvleIRrqI0LkiftCXKWVSrzOiV+O9GkKx1byw1yAIZus
QdwMT7X0O9hrZLZwhICWC9s6cGhnlCVxLIP/+JkVK7hxEq/LxoSszNV77wIDAQAB
AoGAa2G69L7quil7VMBmI6lqbtyJfNAsrXtpIq8eG/z4qsZ076ObAKTI/XeldcoH
57CZL+xXVKU64umZMt0rleJuGXdlauEUbsSx+biGewRfGTgC4rUSjmE539rBvmRW
gaKliorepPMp/+B9CcG/2YfDPRvG/2cgTXJHVvneo+xHL4ECQQD2Jx5Mvs8z7s2E
jY1mkpRKqh4Z7rlitkAwe1NXcVC8hz5ASu7ORyTl8EPpKAfRMYl1ofK/ozT1URXf
kL5nChPfAkEA4LPUJ6cqrY4xrrtdGaM4iGIxzen5aZlKz/YNlq5LuQKbnLLHMuXU
ohp/ynpqNWbcAFbmtGSMayxGKW5+fJgZ8QJAUBOZv82zCmn9YcnK3juBEmkVMcp/
dKVlbGAyVJgAc9RrY+78kQ6D6mmnLgpfwKYk2ae9mKo3aDbgrsIfrtWQcQJAfFGi
CEpJp3orbLQG319ZsMM7MOTJdC42oPZOMFbAWFzkAX88DKHx0bn9h+XQizkccSej
Ppz+v3DgZJ3YZ1Cz0QJBALiqIokZ+oa3AY6oT0aiec6txrGvNPPbwOsrBpFqGNbu
AByzWWBoBi40eKMSIR30LqN9H8YnJ91Aoy1njGYyQaw=
-----END RSA PRIVATE KEY-----

1
spec/fixtures/files/test.blah vendored Normal file
View File

@ -0,0 +1 @@
test

1
spec/fixtures/files/test.txt vendored Normal file
View File

@ -0,0 +1 @@
Hello world

440
spec/fixtures/files/test_api.json vendored Normal file
View File

@ -0,0 +1,440 @@
{
"kind": "discovery#describeItem",
"name": "test",
"version": "v1",
"id": "test:v1",
"description": "Discovery doc API used for testing the code generator",
"basePath": "/test/",
"rootUrl": "https://www.googleapis.com/",
"servicePath": "test/v1/",
"rpcPath": "/rpc",
"auth": {
"oauth2": {
"scopes": {
"https://www.googleapis.com/auth/test": {
"description": "View and manage things"
},
"https://www.googleapis.com/auth/test.readonly": {
"description": "View things"
}
}
}
},
"parameters": {
"alt": {
"type": "string",
"description": "Data format for the response.",
"default": "json",
"enum": [
"json"
],
"enumDescriptions": [
"Responses with Content-Type of application/json"
],
"location": "query"
},
"fields": {
"type": "string",
"description": "Selector specifying which fields to include in a partial response.",
"location": "query"
},
"key": {
"type": "string",
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query"
},
"oauth_token": {
"type": "string",
"description": "OAuth 2.0 token for the current user.",
"location": "query"
},
"prettyPrint": {
"type": "boolean",
"description": "Returns response with indentations and line breaks.",
"default": "true",
"location": "query"
},
"quotaUser": {
"type": "string",
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
"location": "query"
},
"userIp": {
"type": "string",
"description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
"location": "query"
}
},
"schemas": {
"Thing": {
"id": "Thing",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"etag": {
"type": "string"
},
"kind": {
"type": "string",
"default": "test#thing"
},
"name": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"hat": {
"$ref": "Hat"
},
"properties": {
"$ref": "HashLikeThing"
},
"photo": {
"type": "object",
"properties": {
"filename": {
"type": "string"
},
"hash": {
"type": "string"
},
"hashAlgorithm": {
"type": "string"
},
"size": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
},
"Hat": {
"type": "object",
"variant": {
"discriminant": "type",
"map": [
{
"type_value": "topHat",
"$ref": "TopHat"
},
{
"type_value": "baseballHat",
"$ref": "BaseballHat"
}
]
}
},
"TopHat": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"height": {
"type": "number"
}
}
},
"BaseballHat": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"color": {
"type": "string"
}
}
},
"HashLikeThing": {
"id": "HashLikeThing",
"type": "object",
"additionalProperties": {
"type": "string",
"description": "A mapping from export format to URL"
}
},
"TestThing": {
"id": "TestThing",
"type": "object",
"properties" :{}
},
"TestAnotherThing": {
"id": "TestAnotherThing",
"type": "object",
"properties" :{}
},
"ThingList": {
"id": "ThingList",
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "The actual list of files.",
"items": {
"$ref": "Thing"
}
},
"kind": {
"type": "string",
"default": "test#thinglist"
}
}
},
"QueryResults": {
"id": "QueryResults",
"type": "object",
"properties": {
"rows": {
"$ref": "Rows"
}
}
},
"Rows": {
"id": "QueryResults",
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}
}
},
"methods": {
"query": {
"path": "query",
"id": "test.query",
"httpMethod": "GET",
"response": {
"$ref": "QueryResults"
//"$ref": "Rows" TODO: Support naked collections as a return value
},
"parameters": {
"s": {
"type": "string",
"location": "query",
"required": false,
"repeated": false
},
"i": {
"type": "integer",
"location": "query",
"required": false,
"repeated": false,
"minimum": "0",
"maximum": "4294967295",
"default": "20"
},
"n": {
"type": "number",
"location": "query",
"required": false,
"repeated": false
},
"b": {
"type": "boolean",
"location": "query",
"required": false,
"repeated": false
},
"a": {
"type": "any",
"location": "query",
"required": false,
"repeated": false
},
"e": {
"type": "string",
"location": "query",
"required": false,
"repeated": false,
"enum": [
"foo",
"bar"
]
},
"er": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"enum": [
"one",
"two",
"three"
]
},
"sr": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"pattern": "[a-z]+"
},
"do": {
"type": "string",
"location": "query",
"required": false
}
}
}
},
"resources": {
"things": {
"resources": {
"subthings": {
"methods": {
"list": {
"path": "things",
"id": "test.things.subthings.list",
"httpMethod": "GET",
"parameters": {
"max-results": {
"type": "number",
"location": "query",
"required": false
}
},
"response": {
"$ref": "ThingList"
}
}
}
}
},
"methods": {
"list": {
"path": "things",
"id": "test.things.list",
"httpMethod": "GET",
"parameters": {
"max-results": {
"type": "number",
"location": "query",
"required": false
}
},
"response": {
"$ref": "ThingList"
}
},
"delete": {
"path": "things/{id}",
"id": "test.things.delete",
"httpMethod": "DELETE",
"description": "Delete things",
"parameters": {
"id": {
"location": "path",
"required": true,
"description": "ID of the thing to delete",
"type": "string"
}
},
"parameterOrder": [
"id"
]
},
"get": {
"path": "things/{id}",
"id": "test.things.get",
"httpMethod": "GET",
"description": "Get things",
"supportsMediaDownload": true,
"parameters": {
"id": {
"location": "path",
"required": true,
"description": "ID of the thing to get",
"type": "string"
}
},
"supportsMediaDownload": true,
"parameterOrder": [
"id"
],
"response": {
"$ref": "Thing"
}
},
"create": {
"path": "things",
"id": "test.things.create",
"httpMethod": "POST",
"description": "Create things",
"request": {
"$ref": "Thing"
},
"response": {
"$ref": "Thing"
},
"supportsMediaUpload": true,
"mediaUpload": {
"accept": [
"*/*"
],
"maxSize": "1KB",
"protocols": {
"simple": {
"multipart": true,
"path": "upload/things/{id}"
},
"resumable": {
"multipart": true,
"path": "/resumable/upload/things/{id}"
}
}
}
},
"update": {
"path": "things/{id}",
"id": "test.things.update",
"httpMethod": "PUT",
"description": "Update things",
"parameters": {
"id": {
"location": "path",
"description": "ID of the thing to update",
"type": "string"
}
},
"parameterOrder": [
"id"
],
"request": {
"$ref": "Thing"
},
"response": {
"$ref": "Thing"
},
"supportsMediaUpload": true,
"mediaUpload": {
"accept": [
"*/*"
],
"maxSize": "1KB",
"protocols": {
"simple": {
"multipart": true,
"path": "upload/things/{id}"
},
"resumable": {
"multipart": true,
"path": "/resumable/upload/things/{id}"
}
}
}
}
}
}
}
}

View File

@ -1,584 +0,0 @@
{
"kind": "discovery#describeItem",
"name": "zoo",
"version": "v1",
"description": "Zoo API used for testing",
"basePath": "/zoo/",
"rootUrl": "https://www.googleapis.com/",
"servicePath": "zoo/v1/",
"rpcPath": "/rpc",
"parameters": {
"alt": {
"type": "string",
"description": "Data format for the response.",
"default": "json",
"enum": [
"json"
],
"enumDescriptions": [
"Responses with Content-Type of application/json"
],
"location": "query"
},
"fields": {
"type": "string",
"description": "Selector specifying which fields to include in a partial response.",
"location": "query"
},
"key": {
"type": "string",
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query"
},
"oauth_token": {
"type": "string",
"description": "OAuth 2.0 token for the current user.",
"location": "query"
},
"prettyPrint": {
"type": "boolean",
"description": "Returns response with indentations and line breaks.",
"default": "true",
"location": "query"
},
"quotaUser": {
"type": "string",
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
"location": "query"
},
"userIp": {
"type": "string",
"description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
"location": "query"
}
},
"features": [
"dataWrapper"
],
"schemas": {
"Animal": {
"id": "Animal",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"kind": {
"type": "string",
"default": "zoo#animal"
},
"name": {
"type": "string"
},
"photo": {
"type": "object",
"properties": {
"filename": {
"type": "string"
},
"hash": {
"type": "string"
},
"hashAlgorithm": {
"type": "string"
},
"size": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
},
"Animal2": {
"id": "Animal2",
"type": "object",
"properties": {
"kind": {
"type": "string",
"default": "zoo#animal"
},
"name": {
"type": "string"
}
}
},
"AnimalFeed": {
"id": "AnimalFeed",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "Animal"
}
},
"kind": {
"type": "string",
"default": "zoo#animalFeed"
}
}
},
"AnimalMap": {
"id": "AnimalMap",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"animals": {
"type": "object",
"description": "Map of animal id to animal data",
"additionalProperties": {
"$ref": "Animal"
}
},
"kind": {
"type": "string",
"default": "zoo#animalMap"
}
}
},
"LoadFeed": {
"id": "LoadFeed",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"doubleVal": {
"type": "number"
},
"nullVal": {
"type": "null"
},
"booleanVal": {
"type": "boolean",
"description": "True or False."
},
"anyVal": {
"type": "any",
"description": "Anything will do."
},
"enumVal": {
"type": "string"
},
"kind": {
"type": "string",
"default": "zoo#loadValue"
},
"longVal": {
"type": "integer"
},
"stringVal": {
"type": "string"
}
}
}
},
"kind": {
"type": "string",
"default": "zoo#loadFeed"
}
}
}
},
"methods": {
"query": {
"path": "query",
"id": "bigquery.query",
"httpMethod": "GET",
"parameters": {
"q": {
"type": "string",
"location": "query",
"required": false,
"repeated": false
},
"i": {
"type": "integer",
"location": "query",
"required": false,
"repeated": false,
"minimum": "0",
"maximum": "4294967295",
"default": "20"
},
"n": {
"type": "number",
"location": "query",
"required": false,
"repeated": false
},
"b": {
"type": "boolean",
"location": "query",
"required": false,
"repeated": false
},
"a": {
"type": "any",
"location": "query",
"required": false,
"repeated": false
},
"o": {
"type": "object",
"location": "query",
"required": false,
"repeated": false
},
"e": {
"type": "string",
"location": "query",
"required": false,
"repeated": false,
"enum": [
"foo",
"bar"
]
},
"er": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"enum": [
"one",
"two",
"three"
]
},
"rr": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"pattern": "[a-z]+"
}
}
}
},
"resources": {
"my": {
"resources": {
"favorites": {
"methods": {
"list": {
"path": "favorites/@me/mine",
"id": "zoo.animals.mine",
"httpMethod": "GET",
"parameters": {
"max-results": {
"location": "query",
"required": false
}
}
}
}
}
}
},
"global": {
"resources": {
"print": {
"methods": {
"assert": {
"path": "global/print/assert",
"id": "zoo.animals.mine",
"httpMethod": "GET",
"parameters": {
"max-results": {
"location": "query",
"required": false
}
}
}
}
}
}
},
"animals": {
"methods": {
"crossbreed": {
"path": "animals/crossbreed",
"id": "zoo.animals.crossbreed",
"httpMethod": "POST",
"description": "Cross-breed animals",
"response": {
"$ref": "Animal2"
},
"mediaUpload": {
"accept": [
"image/png"
],
"protocols": {
"simple": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
},
"resumable": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
}
}
}
},
"delete": {
"path": "animals/{name}",
"id": "zoo.animals.delete",
"httpMethod": "DELETE",
"description": "Delete animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to delete",
"type": "string"
}
},
"parameterOrder": [
"name"
]
},
"get": {
"path": "animals/{name}",
"id": "zoo.animals.get",
"httpMethod": "GET",
"description": "Get animals",
"supportsMediaDownload": true,
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to load",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include everything"
]
}
},
"parameterOrder": [
"name"
],
"response": {
"$ref": "Animal"
}
},
"getmedia": {
"path": "animals/{name}",
"id": "zoo.animals.get",
"httpMethod": "GET",
"description": "Get animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to load",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include everything"
]
}
},
"parameterOrder": [
"name"
]
},
"insert": {
"path": "animals",
"id": "zoo.animals.insert",
"httpMethod": "POST",
"description": "Insert animals",
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
},
"mediaUpload": {
"accept": [
"image/png"
],
"maxSize": "1KB",
"protocols": {
"simple": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
},
"resumable": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
}
}
}
},
"list": {
"path": "animals",
"id": "zoo.animals.list",
"httpMethod": "GET",
"description": "List animals",
"parameters": {
"max-results": {
"location": "query",
"description": "Maximum number of results to return",
"type": "integer",
"minimum": "0"
},
"name": {
"location": "query",
"description": "Restrict result to animals with this name",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include absolutely everything"
]
},
"start-token": {
"location": "query",
"description": "Pagination token",
"type": "string"
}
},
"response": {
"$ref": "AnimalFeed"
}
},
"patch": {
"path": "animals/{name}",
"id": "zoo.animals.patch",
"httpMethod": "PATCH",
"description": "Update animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to update",
"type": "string"
}
},
"parameterOrder": [
"name"
],
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
}
},
"update": {
"path": "animals/{name}",
"id": "zoo.animals.update",
"httpMethod": "PUT",
"description": "Update animals",
"parameters": {
"name": {
"location": "path",
"description": "Name of the animal to update",
"type": "string"
}
},
"parameterOrder": [
"name"
],
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
}
}
}
},
"load": {
"methods": {
"list": {
"path": "load",
"id": "zoo.load.list",
"httpMethod": "GET",
"response": {
"$ref": "LoadFeed"
}
}
}
},
"loadNoTemplate": {
"methods": {
"list": {
"path": "loadNoTemplate",
"id": "zoo.loadNoTemplate.list",
"httpMethod": "GET"
}
}
},
"scopedAnimals": {
"methods": {
"list": {
"path": "scopedanimals",
"id": "zoo.scopedAnimals.list",
"httpMethod": "GET",
"description": "List animals (scoped)",
"parameters": {
"max-results": {
"location": "query",
"description": "Maximum number of results to return",
"type": "integer",
"minimum": "0"
},
"name": {
"location": "query",
"description": "Restrict result to animals with this name",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include absolutely everything"
]
},
"start-token": {
"location": "query",
"description": "Pagination token",
"type": "string"
}
},
"response": {
"$ref": "AnimalFeed"
}
}
}
}
}
}

View File

@ -1,10 +1,8 @@
require 'spec_helper' require 'spec_helper'
require 'google/api_client' require 'google/api_client/auth/storage'
require 'google/api_client/version'
describe Google::APIClient::Storage do describe Google::APIClient::Storage do
let(:client) { Google::APIClient.new(:application_name => 'API Client Tests') }
let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..')) } let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..')) }
let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) } let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) }

View File

@ -1,7 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'google/api_client' require 'google/api_client/auth/storages/file_store'
require 'google/api_client/version'
describe Google::APIClient::FileStore do describe Google::APIClient::FileStore do
let(:root_path) { File.expand_path(File.join(__FILE__, '..','..','..', '..','..')) } let(:root_path) { File.expand_path(File.join(__FILE__, '..','..','..', '..','..')) }

View File

@ -1,8 +1,6 @@
require 'spec_helper' require 'spec_helper'
require 'google/api_client' require 'google/api_client/auth/storages/redis_store'
require 'google/api_client/version'
describe Google::APIClient::RedisStore do describe Google::APIClient::RedisStore do
let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..', '..')) } let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..', '..')) }

View File

@ -1,248 +0,0 @@
# Copyright 2012 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 'spec_helper'
require 'google/api_client'
RSpec.describe Google::APIClient::BatchRequest do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
after do
# Reset client to not-quite-pristine state
CLIENT.key = nil
CLIENT.user_ip = nil
end
it 'should raise an error if making an empty batch request' do
batch = Google::APIClient::BatchRequest.new
expect(lambda do
CLIENT.execute(batch)
end).to raise_error(Google::APIClient::BatchError)
end
it 'should allow query parameters in batch requests' do
batch = Google::APIClient::BatchRequest.new
batch.add(:uri => 'https://example.com', :parameters => {
'a' => '12345'
})
method, uri, headers, body = batch.to_http_request
expect(body.read).to include("/?a=12345")
end
describe 'with the discovery API' do
before do
CLIENT.authorization = nil
@discovery = CLIENT.discovered_api('discovery', 'v1')
end
describe 'with two valid requests' do
before do
@call1 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'plus',
'version' => 'v1'
}
}
@call2 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'discovery',
'version' => 'v1'
}
}
end
it 'should execute both when using a global callback' do
block_called = 0
ids = ['first_call', 'second_call']
expected_ids = ids.clone
batch = Google::APIClient::BatchRequest.new do |result|
block_called += 1
expect(result.status).to eq(200)
expect(expected_ids).to include(result.response.call_id)
expected_ids.delete(result.response.call_id)
end
batch.add(@call1, ids[0])
batch.add(@call2, ids[1])
CLIENT.execute(batch)
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
batch = Google::APIClient::BatchRequest.new
call1_returned, call2_returned = false, false
batch.add(@call1) do |result|
call1_returned = true
expect(result.status).to eq(200)
end
batch.add(@call2) do |result|
call2_returned = true
expect(result.status).to eq(200)
end
CLIENT.execute(batch)
expect(call1_returned).to be_truthy
expect(call2_returned).to be_truthy
end
it 'should raise an error if using the same call ID more than once' do
batch = Google::APIClient::BatchRequest.new
expect(lambda do
batch.add(@call1, 'my_id')
batch.add(@call2, 'my_id')
end).to raise_error(Google::APIClient::BatchError)
end
end
describe 'with a valid request and an invalid one' do
before do
@call1 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'plus',
'version' => 'v1'
}
}
@call2 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 0,
'version' => 1
}
}
end
it 'should execute both when using a global callback' do
block_called = 0
ids = ['first_call', 'second_call']
expected_ids = ids.clone
batch = Google::APIClient::BatchRequest.new do |result|
block_called += 1
expect(expected_ids).to include(result.response.call_id)
expected_ids.delete(result.response.call_id)
if result.response.call_id == ids[0]
expect(result.status).to eq(200)
else
expect(result.status).to be >= 400
expect(result.status).to be < 500
end
end
batch.add(@call1, ids[0])
batch.add(@call2, ids[1])
CLIENT.execute(batch)
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
batch = Google::APIClient::BatchRequest.new
call1_returned, call2_returned = false, false
batch.add(@call1) do |result|
call1_returned = true
expect(result.status).to eq(200)
end
batch.add(@call2) do |result|
call2_returned = true
expect(result.status).to be >= 400
expect(result.status).to be < 500
end
CLIENT.execute(batch)
expect(call1_returned).to be_truthy
expect(call2_returned).to be_truthy
end
end
end
describe 'with the calendar API' do
before do
CLIENT.authorization = nil
@calendar = CLIENT.discovered_api('calendar', 'v3')
end
describe 'with two valid requests' do
before do
event1 = {
'summary' => 'Appointment 1',
'location' => 'Somewhere',
'start' => {
'dateTime' => '2011-01-01T10:00:00.000-07:00'
},
'end' => {
'dateTime' => '2011-01-01T10:25:00.000-07:00'
},
'attendees' => [
{
'email' => 'myemail@mydomain.tld'
}
]
}
event2 = {
'summary' => 'Appointment 2',
'location' => 'Somewhere as well',
'start' => {
'dateTime' => '2011-01-02T10:00:00.000-07:00'
},
'end' => {
'dateTime' => '2011-01-02T10:25:00.000-07:00'
},
'attendees' => [
{
'email' => 'myemail@mydomain.tld'
}
]
}
@call1 = {
:api_method => @calendar.events.insert,
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
:body => MultiJson.dump(event1),
:headers => {'Content-Type' => 'application/json'}
}
@call2 = {
:api_method => @calendar.events.insert,
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
:body => MultiJson.dump(event2),
:headers => {'Content-Type' => 'application/json'}
}
end
it 'should convert to a correct HTTP request' do
batch = Google::APIClient::BatchRequest.new { |result| }
batch.add(@call1, '1').add(@call2, '2')
request = batch.to_env(CLIENT.connection)
boundary = Google::APIClient::BatchRequest::BATCH_BOUNDARY
expect(request[:method].to_s.downcase).to eq('post')
expect(request[:url].to_s).to eq('https://www.googleapis.com/batch')
expect(request[:request_headers]['Content-Type']).to eq("multipart/mixed;boundary=#{boundary}")
body = request[:body].read
expect(body).to include(@call1[:body])
expect(body).to include(@call2[:body])
end
end
end
end

View File

@ -1,708 +0,0 @@
# encoding:utf-8
# 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 'spec_helper'
require 'faraday'
require 'multi_json'
require 'compat/multi_json'
require 'signet/oauth_1/client'
require 'google/api_client'
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient do
include ConnectionHelpers
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
after do
# Reset client to not-quite-pristine state
CLIENT.key = nil
CLIENT.user_ip = nil
end
it 'should raise a type error for bogus authorization' do
expect(lambda do
Google::APIClient.new(:application_name => 'API Client Tests', :authorization => 42)
end).to raise_error(TypeError)
end
it 'should not be able to retrieve the discovery document for a bogus API' do
expect(lambda do
CLIENT.discovery_document('bogus')
end).to raise_error(Google::APIClient::TransmissionError)
expect(lambda do
CLIENT.discovered_api('bogus')
end).to raise_error(Google::APIClient::TransmissionError)
end
it 'should raise an error for bogus services' do
expect(lambda do
CLIENT.discovered_api(42)
end).to raise_error(TypeError)
end
it 'should raise an error for bogus services' do
expect(lambda do
CLIENT.preferred_version(42)
end).to raise_error(TypeError)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.execute(42)
end).to raise_error(TypeError)
end
it 'should not return a preferred version for bogus service names' do
expect(CLIENT.preferred_version('bogus')).to eq(nil)
end
describe 'with zoo API' do
it 'should return API instance registered from file' do
zoo_json = File.join(fixtures_path, 'files', 'zoo.json')
contents = File.open(zoo_json, 'rb') { |io| io.read }
api = CLIENT.register_discovery_document('zoo', 'v1', contents)
expect(api).to be_kind_of(Google::APIClient::API)
end
end
describe 'with the prediction API' do
before do
CLIENT.authorization = nil
# The prediction API no longer exposes a v1, so we have to be
# careful about looking up the wrong API version.
@prediction = CLIENT.discovered_api('prediction', 'v1.2')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('prediction')).to be ===
'https://www.googleapis.com/discovery/v1/apis/prediction/v1/rest'
end
it 'should correctly determine the discovery URI if :user_ip is set' do
CLIENT.user_ip = '127.0.0.1'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?userIp=127.0.0.1') do |env|
[200, {}, '{}']
end
end
CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly determine the discovery URI if :key is set' do
CLIENT.key = 'qwerty'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly determine the discovery URI if both are set' do
CLIENT.key = 'qwerty'
CLIENT.user_ip = '127.0.0.1'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty&userIp=127.0.0.1') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly generate API objects' do
expect(CLIENT.discovered_api('prediction', 'v1.2').name).to eq('prediction')
expect(CLIENT.discovered_api('prediction', 'v1.2').version).to eq('v1.2')
expect(CLIENT.discovered_api(:prediction, 'v1.2').name).to eq('prediction')
expect(CLIENT.discovered_api(:prediction, 'v1.2').version).to eq('v1.2')
end
it 'should discover methods' do
expect(CLIENT.discovered_method(
'prediction.training.insert', 'prediction', 'v1.2'
).name).to eq('insert')
expect(CLIENT.discovered_method(
:'prediction.training.insert', :prediction, 'v1.2'
).name).to eq('insert')
expect(CLIENT.discovered_method(
'prediction.training.delete', 'prediction', 'v1.2'
).name).to eq('delete')
end
it 'should define the origin API in discovered methods' do
expect(CLIENT.discovered_method(
'prediction.training.insert', 'prediction', 'v1.2'
).api.name).to eq('prediction')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method(
'prediction.bogus', 'prediction', 'v1.2'
)).to eq(nil)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.discovered_method(42, 'prediction', 'v1.2')
end).to raise_error(TypeError)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.execute(:api_method => CLIENT.discovered_api('prediction', 'v1.2'))
end).to raise_error(TypeError)
end
it 'should correctly determine the preferred version' do
expect(CLIENT.preferred_version('prediction').version).not_to eq('v1')
expect(CLIENT.preferred_version(:prediction).version).not_to eq('v1')
end
it 'should return a batch path' do
expect(CLIENT.discovered_api('prediction', 'v1.2').batch_path).not_to be_nil
end
it 'should generate valid requests' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:body]).to eq('')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate valid requests when parameter value includes semicolon' do
conn = stub_connection do |stub|
# semicolon (;) in parameter value was being converted to
# bare ampersand (&) in 0.4.7. ensure that it gets converted
# to a CGI-escaped semicolon (%3B) instead.
stub.post('/prediction/v1.2/training?data=12345%3B67890') do |env|
expect(env[:body]).to eq('')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345;67890'},
:connection => conn
)
conn.verify
end
it 'should generate valid requests when multivalued parameters are passed' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=1&data=2') do |env|
expect(env.params['data']).to include('1', '2')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => ['1', '2']},
:connection => conn
)
conn.verify
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should allow modification to the base URIs for testing purposes' do
# Using a new client instance here to avoid caching rebased discovery doc
prediction_rebase =
Google::APIClient.new(:application_name => 'API Client Tests').discovered_api('prediction', 'v1.2')
prediction_rebase.method_base =
'https://testing-domain.example.com/prediction/v1.2/'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training') do |env|
expect(env[:url].host).to eq('testing-domain.example.com')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => prediction_rebase.training.insert,
:parameters => {'data' => '123'},
:connection => conn
)
conn.verify
end
it 'should generate OAuth 1 requests' do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:request_headers]).to have_key('Authorization')
expect(env[:request_headers]['Authorization']).to match(/^OAuth/)
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate OAuth 2 requests' do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:request_headers]).to have_key('Authorization')
expect(env[:request_headers]['Authorization']).to match(/^Bearer/)
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should not be able to execute improperly authorized requests' do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
result = CLIENT.execute(
@prediction.training.insert,
{'data' => '12345'}
)
expect(result.response.status).to eq(401)
end
it 'should not be able to execute improperly authorized requests' do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
result = CLIENT.execute(
@prediction.training.insert,
{'data' => '12345'}
)
expect(result.response.status).to eq(401)
end
it 'should not be able to execute improperly authorized requests' do
expect(lambda do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
result = CLIENT.execute!(
@prediction.training.insert,
{'data' => '12345'}
)
end).to raise_error(Google::APIClient::ClientError)
end
it 'should not be able to execute improperly authorized requests' do
expect(lambda do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
result = CLIENT.execute!(
@prediction.training.insert,
{'data' => '12345'}
)
end).to raise_error(Google::APIClient::ClientError)
end
it 'should correctly handle unnamed parameters' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training') do |env|
expect(env[:request_headers]).to have_key('Content-Type')
expect(env[:request_headers]['Content-Type']).to eq('application/json')
[200, {}, '{}']
end
end
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
CLIENT.execute(
:api_method => @prediction.training.insert,
:body => MultiJson.dump({"id" => "bucket/object"}),
:headers => {'Content-Type' => 'application/json'},
:connection => conn
)
conn.verify
end
end
describe 'with the plus API' do
before do
CLIENT.authorization = nil
@plus = CLIENT.discovered_api('plus')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('plus')).to be ===
'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
end
it 'should find APIs that are in the discovery document' do
expect(CLIENT.discovered_api('plus').name).to eq('plus')
expect(CLIENT.discovered_api('plus').version).to eq('v1')
expect(CLIENT.discovered_api(:plus).name).to eq('plus')
expect(CLIENT.discovered_api(:plus).version).to eq('v1')
end
it 'should find methods that are in the discovery document' do
# TODO(bobaman) Fix this when the RPC names are correct
expect(CLIENT.discovered_method(
'plus.activities.list', 'plus'
).name).to eq('list')
end
it 'should define the origin API in discovered methods' do
expect(CLIENT.discovered_method(
'plus.activities.list', 'plus'
).api.name).to eq('plus')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method('plus.bogus', 'plus')).to eq(nil)
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/plus/v1/people/107807692475771887386/activities/public') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {
'userId' => '107807692475771887386', 'collection' => 'public'
},
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly validate parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {'alt' => 'json'},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should correctly validate parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {
'userId' => '107807692475771887386', 'collection' => 'bogus'
},
:authenticated => false
).to_env(CLIENT.connection)
end).to raise_error(ArgumentError)
end
it 'should correctly determine the service root_uri' do
expect(@plus.root_uri.to_s).to eq('https://www.googleapis.com/')
end
end
describe 'with the adsense API' do
before do
CLIENT.authorization = nil
@adsense = CLIENT.discovered_api('adsense', 'v1.3')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('adsense', 'v1.3').to_s).to be ===
'https://www.googleapis.com/discovery/v1/apis/adsense/v1.3/rest'
end
it 'should find APIs that are in the discovery document' do
expect(CLIENT.discovered_api('adsense', 'v1.3').name).to eq('adsense')
expect(CLIENT.discovered_api('adsense', 'v1.3').version).to eq('v1.3')
end
it 'should return a batch path' do
expect(CLIENT.discovered_api('adsense', 'v1.3').batch_path).not_to be_nil
end
it 'should find methods that are in the discovery document' do
expect(CLIENT.discovered_method(
'adsense.reports.generate', 'adsense', 'v1.3'
).name).to eq('generate')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method('adsense.bogus', 'adsense', 'v1.3')).to eq(nil)
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/adclients') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @adsense.adclients.list,
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should not be able to execute requests without authorization' do
result = CLIENT.execute(
:api_method => @adsense.adclients.list,
:authenticated => false
)
expect(result.response.status).to eq(401)
end
it 'should fail when validating missing required parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should succeed when validating parameters in a correct call' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&endDate=2010-01-01&metric=PAGE_VIEWS&startDate=2000-01-01') do |env|
[200, {}, '{}']
end
end
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => 'DATE',
'metric' => 'PAGE_VIEWS'
},
:authenticated => false,
:connection => conn
)
end).not_to raise_error
conn.verify
end
it 'should fail when validating parameters with invalid values' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => 'BAD_CHARACTERS=-&*(£&',
'metric' => 'PAGE_VIEWS'
},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should succeed when validating repeated parameters in a correct call' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&dimension=PRODUCT_CODE'+
'&endDate=2010-01-01&metric=CLICKS&metric=PAGE_VIEWS&'+
'startDate=2000-01-01') do |env|
[200, {}, '{}']
end
end
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'PRODUCT_CODE'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false,
:connection => conn
)
end).not_to raise_error
conn.verify
end
it 'should fail when validating incorrect repeated parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'BAD_CHARACTERS=-&*(£&'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should generate valid requests when multivalued parameters are passed' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&dimension=PRODUCT_CODE'+
'&endDate=2010-01-01&metric=CLICKS&metric=PAGE_VIEWS&'+
'startDate=2000-01-01') do |env|
expect(env.params['dimension']).to include('DATE', 'PRODUCT_CODE')
expect(env.params['metric']).to include('CLICKS', 'PAGE_VIEWS')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'PRODUCT_CODE'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false,
:connection => conn
)
conn.verify
end
end
describe 'with the Drive API' do
before do
CLIENT.authorization = nil
@drive = CLIENT.discovered_api('drive', 'v1')
end
it 'should include media upload info methods' do
expect(@drive.files.insert.media_upload).not_to eq(nil)
end
it 'should include accepted media types' do
expect(@drive.files.insert.media_upload.accepted_types).not_to be_empty
end
it 'should have an upload path' do
expect(@drive.files.insert.media_upload.uri_template).not_to eq(nil)
end
it 'should have a max file size' do
expect(@drive.files.insert.media_upload.max_size).not_to eq(nil)
end
end
describe 'with the Pub/Sub API' do
before do
CLIENT.authorization = nil
@pubsub = CLIENT.discovered_api('pubsub', 'v1beta2')
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/v1beta2/projects/12345/topics') do |env|
expect(env[:url].host).to eq('pubsub.googleapis.com')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @pubsub.projects.topics.list,
:parameters => {'project' => 'projects/12345'},
:connection => conn
)
conn.verify
end
it 'should correctly determine the service root_uri' do
expect(@pubsub.root_uri.to_s).to eq('https://pubsub.googleapis.com/')
end
it 'should discover correct method URIs' do
list = CLIENT.discovered_method(
"pubsub.projects.topics.list", "pubsub", "v1beta2"
)
expect(list.uri_template.pattern).to eq(
"https://pubsub.googleapis.com/v1beta2/{+project}/topics"
)
publish = CLIENT.discovered_method(
"pubsub.projects.topics.publish", "pubsub", "v1beta2"
)
expect(publish.uri_template.pattern).to eq(
"https://pubsub.googleapis.com/v1beta2/{+topic}:publish"
)
end
end
end

Some files were not shown because too many files have changed in this diff Show More