#160 - Add option to set encoding of response body

This commit is contained in:
Steven Bazyl 2014-12-16 12:29:11 -08:00
parent 91ae01aa83
commit fda7288859
5 changed files with 650 additions and 0 deletions

View File

@ -31,6 +31,7 @@ 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)
@ -75,6 +76,9 @@ module Google
# @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_options
# Pass through of options to set on the Faraday connection
def initialize(options={})
@ -119,6 +123,7 @@ module Google
@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
@ -265,10 +270,12 @@ module Google
# @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
##
@ -297,6 +304,7 @@ module Google
# @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'
@ -311,6 +319,7 @@ module Google
end
@discovery_documents["#{api}:#{version}"] =
MultiJson.load(discovery_document)
discovered_api(api, version)
end
##

View File

@ -0,0 +1,33 @@
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

584
spec/fixtures/files/zoo.json vendored Normal file
View File

@ -0,0 +1,584 @@
{
"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

@ -23,6 +23,8 @@ 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)
@ -70,6 +72,15 @@ RSpec.describe Google::APIClient 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

View File

@ -1,3 +1,4 @@
# Encoding: utf-8
# Copyright 2012 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -20,6 +21,7 @@ RSpec.describe Google::APIClient::Gzip do
def create_connection(&block)
Faraday.new do |b|
b.response :charset
b.response :gzip
b.adapter :test do |stub|
stub.get '/', &block
@ -43,6 +45,17 @@ RSpec.describe Google::APIClient::Gzip do
expect(result.body).to eq("Hello world\n")
end
it 'should inflate with the correct charset encoding' do
conn = create_connection do |env|
[200,
{ 'Content-Encoding' => 'deflate', 'Content-Type' => 'application/json;charset=BIG5'},
Base64.decode64('eJxb8nLp7t2VAA8fBCI=')]
end
result = conn.get('/')
expect(result.body.encoding).to eq(Encoding::BIG5)
expect(result.body).to eq('日本語'.encode("BIG5"))
end
describe 'with API Client' do
before do