# frozen_string_literal: true # Copyright 2018 Google LLC # # 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 # # https://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 "gapic/path_template" require "gapic/ruby_info" require "gapic/helpers/namespace_helper" module Gapic module Presenters ## # A presenter for rpc methods. # class MethodPresenter include Gapic::Helpers::NamespaceHelper def initialize service_presenter, api, method @service_presenter = service_presenter @api = api @method = method end def service @service_presenter end def name @name ||= begin candidate = ActiveSupport::Inflector.underscore @method.name candidate = "call_#{candidate}" if Gapic::RubyInfo.excluded_method_names.include? candidate candidate end end def kind if client_streaming? if server_streaming? :bidi else :client end elsif server_streaming? :server else :normal end end def doc_description @method.docs_leading_comments end def doc_response_type ret = return_type ret = "::Gapic::Operation" if lro? if server_streaming? ret = "::Enumerable<#{ret}>" elsif paged? paged_type = paged_response_type paged_type = "::Gapic::Operation" if paged_type == "::Google::Longrunning::Operation" ret = "::Gapic::PagedEnumerable<#{paged_type}>" end ret end def arguments arguments = @method.input.fields.reject(&:output_only?) arguments.map { |arg| FieldPresenter.new @api, @method.input, arg } end def fields @method.input.fields.map { |field| FieldPresenter.new @api, @method.input, field } end def fields_with_first_oneof return fields if @method.input.oneof_decl.empty? selected_fields = [] have_oneof = [] @method.input.fields.each do |field| idx = field.oneof_index selected_fields << field unless have_oneof.include? idx have_oneof << idx end selected_fields.map { |field| FieldPresenter.new @api, @method.input, field } end def request_type message_ruby_type @method.input end def return_type message_ruby_type @method.output end def yields? # Server streaming RCP calls are the only one that does not yield !server_streaming? end def yield_doc_description return "Register a callback to be run when an operation is done." if lro? "Access the result along with the RPC operation" end def yield_params if lro? return [ OpenStruct.new( name: "operation", doc_types: "::Gapic::Operation" ) ] end [ OpenStruct.new( name: "result", doc_types: return_type ), OpenStruct.new( name: "operation", doc_types: "::GRPC::ActiveCall::Operation" ) ] end # @api.incode samples and sample_configs are yaml configuration files such as # speech_transcribe_sync_gcs.yaml def samples sample_configs = @api.incode_samples.select do |sample_config| sample_config["service"] == @method.address[0...-1].join(".") && sample_config["rpc"] == @method.name end sample_configs.map { |sample_config| SamplePresenter.new @api, sample_config } end def lro? return paged_response_type == "::Google::Longrunning::Operation" if paged? message_ruby_type(@method.output) == "::Google::Longrunning::Operation" end def client_streaming? @method.client_streaming end def server_streaming? @method.server_streaming end def paged? return false if server_streaming? # Cannot page a streaming response # HACK(dazuma, 2020-04-06): Two specific RPCs should not be paged. # This is an intentionally hard-coded exception (and a temporary one, # to be removed when these methods no longer conform to AIP-4233.) For # detailed information, see internal link go/actools-talent-pagination. address = @method.address.join "." return false if address == "google.cloud.talent.v4beta1.ProfileService.SearchProfiles" return false if address == "google.cloud.talent.v4beta1.JobService.SearchJobs" return false if address == "google.cloud.talent.v4beta1.JobService.SearchJobsForAlert" paged_request?(@method.input) && paged_response?(@method.output) end def paged_response_type return nil unless paged_response? @method.output repeated_field = @method.output.fields.find do |f| f.label == Google::Protobuf::FieldDescriptorProto::Label::LABEL_REPEATED && f.type == Google::Protobuf::FieldDescriptorProto::Type::TYPE_MESSAGE end message_ruby_type repeated_field.message end ## # # @return [Array] The segment key names. # def routing_params segments = Gapic::PathTemplate.parse method_path segments.select { |s| s.is_a? Gapic::PathTemplate::Segment }.map(&:name) end def routing_params? routing_params.any? end def grpc_service_config if @api.grpc_service_config&.service_method_level_configs&.key?(service.grpc_full_name) && @api.grpc_service_config.service_method_level_configs[service.grpc_full_name]&.key?(grpc_method_name) @api.grpc_service_config.service_method_level_configs[service.grpc_full_name][grpc_method_name] end end def grpc_method_name @method.name end protected def message_ruby_type message ruby_namespace @api, message.address.join(".") end def doc_types_for arg if arg.message? "#{message_ruby_type arg.message}, Hash" elsif arg.enum? # TODO: handle when arg message is nil and enum is the type message_ruby_type arg.enum else case arg.type when 1, 2 then "::Float" when 3, 4, 5, 6, 7, 13, 15, 16, 17, 18 then "::Integer" when 9, 12 then "::String" when 8 then "::Boolean" else "::Object" end end end def doc_desc_for arg return nil if arg.docs.leading_comments.empty? arg.docs.leading_comments end def default_value_for arg if arg.message? "{}" elsif arg.enum? # TODO: select the first non-0 enum value # ":ENUM" arg.enum.values.first else case arg.type when 1, 2 then "3.14" when 3, 4, 5, 6, 7, 13, 15, 16, 17, 18 then "42" when 9, 12 then "\"hello world\"" when 8 then "true" else "::Object" end end end def method_path return "" if @method.http.nil? method = [ @method.http.get, @method.http.post, @method.http.put, @method.http.patch, @method.http.delete ].find { |x| !x.empty? } return method unless method.nil? return @method.http.custom.path unless @method.http.custom.nil? end def paged_request? request page_token = request.fields.find do |f| f.name == "page_token" && f.type == Google::Protobuf::FieldDescriptorProto::Type::TYPE_STRING end return false if page_token.nil? page_size_types = [ Google::Protobuf::FieldDescriptorProto::Type::TYPE_INT32, Google::Protobuf::FieldDescriptorProto::Type::TYPE_INT64 ] page_size = request.fields.find do |f| f.name == "page_size" && page_size_types.include?(f.type) end return false if page_size.nil? true end def paged_response? response next_page_token = response.fields.find do |f| f.name == "next_page_token" && f.type == Google::Protobuf::FieldDescriptorProto::Type::TYPE_STRING end return false if next_page_token.nil? repeated_field = response.fields.find do |f| f.label == Google::Protobuf::FieldDescriptorProto::Label::LABEL_REPEATED && f.type == Google::Protobuf::FieldDescriptorProto::Type::TYPE_MESSAGE end return false if repeated_field.nil? # We want to make sure the first repeated field is also has the lowest field number, # but the google-protobuf gem sorts fields by number, so we lose the original order. true end end end end