From d1eff2b2662027f55331d4b93f238d2558f6c2a7 Mon Sep 17 00:00:00 2001 From: Yi-Ru Lin Date: Thu, 10 Sep 2026 22:25:40 -0500 Subject: [PATCH] fix(gapic-common): ignore inherited Service constants in normalize_service REST stubs have no sibling Service constant, so const_defined? with the default inherit: true walks up to Object. An application-level ::Service is then treated as a gRPC Service and service_name is called on it, raising NoMethodError during client construction. Look up Service and Rest with inherit: false so only locally defined constants are considered. Fixes #70 --- gapic-common/lib/gapic/logging_concerns.rb | 8 +- .../test/gapic/logging_concerns_test.rb | 93 +++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/gapic-common/lib/gapic/logging_concerns.rb b/gapic-common/lib/gapic/logging_concerns.rb index 78ae13d..cc590d4 100644 --- a/gapic-common/lib/gapic/logging_concerns.rb +++ b/gapic-common/lib/gapic/logging_concerns.rb @@ -200,12 +200,14 @@ def normalize_service input input when Class mod = input.name.split("::")[..-2].inject(Object) { |m, n| m.const_get n } - if mod.const_defined? "Service" - mod.const_get("Service").service_name + # inherit: false so a top-level ::Service is not mistaken for the + # generated gRPC Service sibling that REST stubs do not define. + if mod.const_defined? "Service", false + mod.const_get("Service", false).service_name else name_segments = input.name.split("::")[..-3] mod = name_segments.inject(Object) { |m, n| m.const_get n } - name_segments.join "." if mod.const_defined? "Rest" + name_segments.join "." if mod.const_defined? "Rest", false end end end diff --git a/gapic-common/test/gapic/logging_concerns_test.rb b/gapic-common/test/gapic/logging_concerns_test.rb index b82529f..e3955f3 100644 --- a/gapic-common/test/gapic/logging_concerns_test.rb +++ b/gapic-common/test/gapic/logging_concerns_test.rb @@ -15,8 +15,101 @@ require "test_helper" require "gapic/logging_concerns" +require "gapic/rest" + +module NormalizeServiceFixtures + module V1 + module ExampleService + class Stub; end + + module Service + def self.service_name + "example.v1.ExampleService" + end + end + + module Rest + class ServiceStub; end + end + end + end + + module Plain + class Stub; end + end +end describe Gapic::LoggingConcerns do + describe ".normalize_service" do + def with_toplevel_constant name, klass + name = name.to_sym + existed = Object.const_defined? name, false + original = Object.const_get name, false if existed + Object.send :remove_const, name if existed + Object.const_set name, klass + yield + ensure + Object.send :remove_const, name if Object.const_defined? name, false + Object.const_set name, original if existed + end + + it "returns a string unchanged" do + input = "google.example.v1.Foo" + assert_equal input, Gapic::LoggingConcerns.normalize_service(input) + end + + it "returns nil for an unrecognized input" do + assert_nil Gapic::LoggingConcerns.normalize_service(nil) + assert_nil Gapic::LoggingConcerns.normalize_service(:symbol) + end + + it "uses a sibling Service.service_name for gRPC stubs" do + result = Gapic::LoggingConcerns.normalize_service NormalizeServiceFixtures::V1::ExampleService::Stub + assert_equal "example.v1.ExampleService", result + end + + it "falls back to a dotted name for REST stubs" do + result = Gapic::LoggingConcerns.normalize_service( + NormalizeServiceFixtures::V1::ExampleService::Rest::ServiceStub + ) + assert_equal "NormalizeServiceFixtures.V1.ExampleService", result + end + + it "ignores an unrelated top-level Service when resolving REST stubs" do + with_toplevel_constant :Service, Class.new do + result = Gapic::LoggingConcerns.normalize_service( + NormalizeServiceFixtures::V1::ExampleService::Rest::ServiceStub + ) + assert_equal "NormalizeServiceFixtures.V1.ExampleService", result + end + end + + it "still uses a sibling Service when an unrelated top-level Service exists" do + with_toplevel_constant :Service, Class.new do + result = Gapic::LoggingConcerns.normalize_service NormalizeServiceFixtures::V1::ExampleService::Stub + assert_equal "example.v1.ExampleService", result + end + end + + it "does not treat a top-level Rest as a REST namespace" do + with_toplevel_constant :Rest, Class.new do + result = Gapic::LoggingConcerns.normalize_service NormalizeServiceFixtures::Plain::Stub + assert_nil result + end + end + + it "does not raise when constructing a REST client stub if a top-level Service exists" do + rest_stub = NormalizeServiceFixtures::V1::ExampleService::Rest::ServiceStub + with_toplevel_constant :Service, Class.new do + stub = Gapic::Rest::ClientStub.new endpoint: "google.example.com", + credentials: :dummy_credentials, + service_name: rest_stub, + logger: nil + assert_nil stub.logger + end + end + end + describe "random_uuid4" do it "outputs the correct format" do output = Gapic::LoggingConcerns.random_uuid4