lib/google/cloud/translate/service.rb in google-cloud-translate-0.21.0 vs lib/google/cloud/translate/service.rb in google-cloud-translate-0.22.0

- old
+ new

@@ -12,76 +12,183 @@ # See the License for the specific language governing permissions and # limitations under the License. require "google/cloud/errors" +require "google/cloud/translate/credentials" require "google/cloud/translate/version" -require "google/apis/translate_v2" +require "faraday" # comes from googleauth, comes from google-cloud-core module Google module Cloud module Translate ## # @private - # Represents the service to Translate, exposing the API calls. - class Service - ## - # Alias to the Google Client API module - API = Google::Apis::TranslateV2 + # Represents the Translate REST service, exposing the API calls. + class Service #:nodoc: + API_VERSION = "v2" + API_URL = "https://translation.googleapis.com" - attr_accessor :credentials + # @private + attr_accessor :project, :credentials, :retries, :timeout, :key ## # Creates a new Service instance. - def initialize key, retries: nil, timeout: nil - @service = API::TranslateService.new - @service.client_options.application_name = "gcloud-ruby" - @service.client_options.application_version = \ - Google::Cloud::Translate::VERSION - @service.request_options.retries = retries || 3 - @service.request_options.timeout_sec = timeout - @service.request_options.open_timeout_sec = timeout - @service.authorization = nil - @service.key = key + def initialize project, credentials, retries: nil, timeout: nil, + key: nil + @project = project + @credentials = credentials + @retries = retries + @timeout = timeout + @key = key end - def service - return mocked_service if mocked_service - @service - end - attr_accessor :mocked_service - ## - # Returns API::ListTranslationsResponse - def translate text, to: nil, from: nil, format: nil, cid: nil - execute do - service.list_translations Array(text), to, cid: cid, format: format, - source: from - end + # Returns Hash of ListTranslationsResponse JSON + def translate text, to: nil, from: nil, format: nil, model: nil, + cid: nil + body = { + q: Array(text), target: to, source: from, format: format, + model: model, cid: cid + }.delete_if { |_k, v| v.nil? }.to_json + + post "/language/translate/v2", body end ## # Returns API::ListDetectionsResponse def detect text - execute { service.list_detections Array(text) } + body = { q: Array(text) }.to_json + + post "language/translate/v2/detect", body end ## # Returns API::ListLanguagesResponse def languages language = nil - execute { service.list_languages target: language } + body = { target: language }.to_json + + post "language/translate/v2/languages", body end def inspect "#{self.class}" end protected + def post path, body = nil + response = execute do + http.post path do |req| + req.headers.merge! default_http_headers + req.body = body unless body.nil? + + if @key + req.params = { key: @key } + else + @credentials.sign_http_request req + end + end + end + + return JSON.parse(response.body)["data"] if response.success? + + fail Google::Cloud::Error.gapi_error_class_for(response.status) + rescue Faraday::ConnectionFailed + raise Google::Cloud::ResourceExhaustedError + end + + ## + # The HTTP object that makes calls to API. + # This must be a Faraday object. + def http + @http ||= Faraday.new url: API_URL, request: { + open_timeout: @timeout, timeout: @timeout + }.delete_if { |_k, v| v.nil? } + end + + ## + # The default HTTP headers to be sent on all API calls. + def default_http_headers + @default_http_headers ||= { + "User-Agent" => "gcloud-ruby/#{Google::Cloud::Translate::VERSION}", + "google-cloud-resource-prefix" => "projects/#{@project}", + "Content-Type" => "application/json" + } + end + + ## + # Make a request and apply incremental backoff def execute - yield - rescue Google::Apis::Error => e - raise Google::Cloud::Error.from_error(e) + backoff = Backoff.new retries: retries + backoff.execute do + yield + end + rescue Faraday::ConnectionFailed + raise Google::Cloud::ResourceExhaustedError + end + + ## + # @private Backoff + class Backoff + class << self + attr_accessor :retries + attr_accessor :http_codes + attr_accessor :reasons + attr_accessor :backoff # :nodoc: + end + + # Set the default values + self.retries = 3 + self.http_codes = [500, 503] + self.reasons = %w(rateLimitExceeded userRateLimitExceeded) + self.backoff = ->(retries) { sleep retries.to_i } + + def initialize options = {} #:nodoc: + @max_retries = (options[:retries] || Backoff.retries).to_i + @http_codes = (options[:http_codes] || Backoff.http_codes).to_a + @reasons = (options[:reasons] || Backoff.reasons).to_a + @backoff = options[:backoff] || Backoff.backoff + end + + def execute #:nodoc: + current_retries = 0 + loop do + response = yield # Expecting Faraday::Response + return response if response.success? + break response unless retry? response, current_retries + current_retries += 1 + @backoff.call current_retries + end + end + + protected + + def retry? result, current_retries #:nodoc: + if current_retries < @max_retries + return true if retry_http_code? result + return true if retry_error_reason? result + end + false + end + + def retry_http_code? response #:nodoc: + @http_codes.include? response.status + end + + def retry_error_reason? response #:nodoc: + result = JSON.parse(response.body)["data"] + if result && + result["error"] && + result["error"]["errors"] + Array(result["error"]["errors"]).each do |error| + if error["reason"] && @reasons.include?(error["reason"]) + return true + end + end + end + false + end end end end end end