From 2e0975540e031348155daffeb897753607ab6f43 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 04:37:33 +0000 Subject: [PATCH 1/2] Update x-goog-api-client metadata header to use a interceptor. This change introduces a MetadataInterceptor to handle the addition of developer-token, login-customer-id, linked-customer-id, and x-goog-api-client modification, similar to the Python implementation. The ServiceLookup and generated factories are updated to use this new interceptor. Also added a test for the new interceptor. Co-authored-by: dorasun <10905385+dorasun@users.noreply.github.com> --- codegen/templates/services.rb.erb | 2 + .../interceptors/metadata_interceptor.rb | 73 ++++++++++++++++ lib/google/ads/google_ads/service_lookup.rb | 27 +++--- test/test_metadata_interceptor.rb | 86 +++++++++++++++++++ 4 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 lib/google/ads/google_ads/interceptors/metadata_interceptor.rb create mode 100644 test/test_metadata_interceptor.rb diff --git a/codegen/templates/services.rb.erb b/codegen/templates/services.rb.erb index 53017d35a..2a611c6d0 100644 --- a/codegen/templates/services.rb.erb +++ b/codegen/templates/services.rb.erb @@ -9,6 +9,7 @@ module Google def initialize( logging_interceptor:, error_interceptor:, + metadata_interceptor:, credentials:, metadata:, endpoint:, @@ -17,6 +18,7 @@ module Google @interceptors = [ error_interceptor, logging_interceptor, + metadata_interceptor ].compact @credentials = credentials @metadata = metadata diff --git a/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb b/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb new file mode 100644 index 000000000..815a6e94d --- /dev/null +++ b/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb @@ -0,0 +1,73 @@ +# Encoding: utf-8 +# +# Copyright 2022 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. +# +# Interceptor to add metadata headers to requests. + +require 'grpc/generic/interceptors' +require 'google/protobuf' + +module Google + module Ads + module GoogleAds + module Interceptors + class MetadataInterceptor < GRPC::ClientInterceptor + def initialize(developer_token, login_customer_id, linked_customer_id, use_cloud_org_for_api_access) + super() + @developer_token = developer_token + @login_customer_id = login_customer_id + @linked_customer_id = linked_customer_id + @use_cloud_org_for_api_access = use_cloud_org_for_api_access + end + + def request_response(request:, call:, method:, metadata: {}) + update_metadata(metadata) + yield + end + + def server_streamer(request:, call:, method:, metadata: {}) + update_metadata(metadata) + yield + end + + private + + def update_metadata(metadata) + if !@use_cloud_org_for_api_access + metadata[:"developer-token"] = @developer_token + end + + if @login_customer_id + metadata[:"login-customer-id"] = @login_customer_id.to_s + end + + if @linked_customer_id + metadata[:"linked-customer-id"] = @linked_customer_id.to_s + end + + # The python library iterates over metadata and modifies x-goog-api-client + # Here we can directly access it. + if metadata.key?(:"x-goog-api-client") + # Check if "pb" is already in the header + unless metadata[:"x-goog-api-client"].include?("pb") + metadata[:"x-goog-api-client"] += " pb/#{Gem.loaded_specs["google-protobuf"].version}" + end + end + end + end + end + end + end +end diff --git a/lib/google/ads/google_ads/service_lookup.rb b/lib/google/ads/google_ads/service_lookup.rb index 095d703f0..aa74149f6 100644 --- a/lib/google/ads/google_ads/service_lookup.rb +++ b/lib/google/ads/google_ads/service_lookup.rb @@ -1,5 +1,6 @@ require 'google/ads/google_ads/interceptors/logging_interceptor' require 'google/ads/google_ads/interceptors/error_interceptor' +require 'google/ads/google_ads/interceptors/metadata_interceptor' module Google module Ads @@ -26,16 +27,23 @@ def call logging_interceptor = GoogleAds::Interceptors::LoggingInterceptor.new(logger) end error_interceptor = GoogleAds::Interceptors::ErrorInterceptor.new + metadata_interceptor = GoogleAds::Interceptors::MetadataInterceptor.new( + config.developer_token, + config.login_customer_id, + config.linked_customer_id, + config.use_cloud_org_for_api_access + ) version_alternates = {} Factories::VERSIONS.each do |v| - version_alternates[v] = factory_at_version(v, error_interceptor, logging_interceptor) + version_alternates[v] = factory_at_version(v, error_interceptor, logging_interceptor, metadata_interceptor) end highest_factory = factory_at_version( Factories::HIGHEST_VERSION, error_interceptor, logging_interceptor, + metadata_interceptor, ) VersionAlternate.new(highest_factory, version_alternates) @@ -43,10 +51,11 @@ def call private - def factory_at_version(version, error_interceptor, logging_interceptor) + def factory_at_version(version, error_interceptor, logging_interceptor, metadata_interceptor) factory = Factories.at_version(version).services.new(**{ logging_interceptor: logging_interceptor, error_interceptor: error_interceptor, + metadata_interceptor: metadata_interceptor, deprecation: deprecator }.merge(gax_service_params)) @@ -62,27 +71,15 @@ def gax_service_params end def headers - headers = {} - - # If config.use_cloud_org_for_api_access is not True, add the developer - # token to the request's metadata - if !config.use_cloud_org_for_api_access - headers[:"developer-token"] = config.developer_token - end - if config.login_customer_id validate_customer_id(:login_customer_id) - # header values must be strings - headers[:"login-customer-id"] = config.login_customer_id.to_s end if config.linked_customer_id validate_customer_id(:linked_customer_id) - # header values must be strings - headers[:"linked-customer-id"] = config.linked_customer_id.to_s end - headers + {} end def validate_customer_id(field) diff --git a/test/test_metadata_interceptor.rb b/test/test_metadata_interceptor.rb new file mode 100644 index 000000000..27ccbe9e2 --- /dev/null +++ b/test/test_metadata_interceptor.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 + +require 'minitest/autorun' +require 'google/ads/google_ads/interceptors/metadata_interceptor' +require 'google/protobuf' + +class TestMetadataInterceptor < Minitest::Test + attr_reader :mi + + def setup + @mi = Google::Ads::GoogleAds::Interceptors::MetadataInterceptor.new( + "dev_token", + "login_id", + "linked_id", + false + ) + end + + def test_adds_developer_token_if_not_cloud_org + metadata = {} + mi.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_equal "dev_token", metadata[:"developer-token"] + end + + def test_adds_login_and_linked_customer_id + metadata = {} + mi.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_equal "login_id", metadata[:"login-customer-id"] + assert_equal "linked_id", metadata[:"linked-customer-id"] + end + + def test_appends_pb_version_to_x_goog_api_client + metadata = { :"x-goog-api-client" => "gl-ruby/1.2.3" } + mi.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_includes metadata[:"x-goog-api-client"], "pb/#{Gem.loaded_specs["google-protobuf"].version}" + end + + def test_does_not_duplicate_pb_version + metadata = { :"x-goog-api-client" => "gl-ruby/1.2.3 pb/1.2.3" } + mi.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_equal "gl-ruby/1.2.3 pb/1.2.3", metadata[:"x-goog-api-client"] + end + + def test_skips_developer_token_if_cloud_org + mi_cloud = Google::Ads::GoogleAds::Interceptors::MetadataInterceptor.new( + "dev_token", + "login_id", + "linked_id", + true + ) + metadata = {} + mi_cloud.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_nil metadata[:"developer-token"] + end +end From 9b768e11bbe3ea8463f74918306393fc5c2ba218 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 04:45:02 +0000 Subject: [PATCH 2/2] Update x-goog-api-client metadata header to use a interceptor. This change introduces a MetadataInterceptor to handle the addition of developer-token, login-customer-id, linked-customer-id, and x-goog-api-client modification, similar to the Python implementation. The ServiceLookup and generated factories are updated to use this new interceptor. Also added a test for the new interceptor. Additionally, implemented gaada configuration support as requested. Co-authored-by: dorasun <10905385+dorasun@users.noreply.github.com> --- lib/google/ads/google_ads/config.rb | 2 ++ .../ads/google_ads/google_ads_client.rb | 1 + .../interceptors/metadata_interceptor.rb | 7 ++++- lib/google/ads/google_ads/service_lookup.rb | 3 ++- test/test_config.rb | 6 +++++ test/test_metadata_interceptor.rb | 26 +++++++++++++++++-- 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/google/ads/google_ads/config.rb b/lib/google/ads/google_ads/config.rb index 105099e8a..5838f807d 100644 --- a/lib/google/ads/google_ads/config.rb +++ b/lib/google/ads/google_ads/config.rb @@ -34,6 +34,7 @@ class Config attr_accessor :login_customer_id attr_accessor :linked_customer_id attr_accessor :use_cloud_org_for_api_access + attr_accessor :gaada attr_accessor :log_level attr_accessor :log_target @@ -59,6 +60,7 @@ def initialize(&block) @login_customer_id = nil @linked_customer_id = nil @use_cloud_org_for_api_access = false + @gaada = nil @log_level = nil @log_target = nil diff --git a/lib/google/ads/google_ads/google_ads_client.rb b/lib/google/ads/google_ads/google_ads_client.rb index 75347a8b1..898da9c92 100644 --- a/lib/google/ads/google_ads/google_ads_client.rb +++ b/lib/google/ads/google_ads/google_ads_client.rb @@ -114,6 +114,7 @@ def load_environment_config if @config.use_cloud_org_for_api_access.is_a?(String) @config.use_cloud_org_for_api_access = @config.use_cloud_org_for_api_access.downcase == "true" end + @config.gaada = ENV.fetch("GOOGLE_ADS_GAADA", @config.gaada) end # Return a service for the provided entity type. For example, passing diff --git a/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb b/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb index 815a6e94d..799ee2d90 100644 --- a/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb +++ b/lib/google/ads/google_ads/interceptors/metadata_interceptor.rb @@ -24,12 +24,13 @@ module Ads module GoogleAds module Interceptors class MetadataInterceptor < GRPC::ClientInterceptor - def initialize(developer_token, login_customer_id, linked_customer_id, use_cloud_org_for_api_access) + def initialize(developer_token, login_customer_id, linked_customer_id, use_cloud_org_for_api_access, gaada) super() @developer_token = developer_token @login_customer_id = login_customer_id @linked_customer_id = linked_customer_id @use_cloud_org_for_api_access = use_cloud_org_for_api_access + @gaada = gaada end def request_response(request:, call:, method:, metadata: {}) @@ -60,6 +61,10 @@ def update_metadata(metadata) # The python library iterates over metadata and modifies x-goog-api-client # Here we can directly access it. if metadata.key?(:"x-goog-api-client") + if @gaada + metadata[:"x-goog-api-client"] += " gaada/#{@gaada}" + end + # Check if "pb" is already in the header unless metadata[:"x-goog-api-client"].include?("pb") metadata[:"x-goog-api-client"] += " pb/#{Gem.loaded_specs["google-protobuf"].version}" diff --git a/lib/google/ads/google_ads/service_lookup.rb b/lib/google/ads/google_ads/service_lookup.rb index aa74149f6..e0d632df0 100644 --- a/lib/google/ads/google_ads/service_lookup.rb +++ b/lib/google/ads/google_ads/service_lookup.rb @@ -31,7 +31,8 @@ def call config.developer_token, config.login_customer_id, config.linked_customer_id, - config.use_cloud_org_for_api_access + config.use_cloud_org_for_api_access, + config.gaada ) version_alternates = {} diff --git a/test/test_config.rb b/test/test_config.rb index f8dd28c21..0cc3fa99d 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -27,18 +27,21 @@ def test_initialize() client_id_value = 'client id' client_secret_value = 'client_secret' developer_token_value = 'developer_token' + gaada_value = 'gaada_value' config = Google::Ads::GoogleAds::Config.new do |c| c.refresh_token = refresh_token_value c.client_id = client_id_value c.client_secret = client_secret_value c.developer_token = developer_token_value + c.gaada = gaada_value end assert_equal(refresh_token_value, config.refresh_token) assert_equal(client_id_value, config.client_id) assert_equal(client_secret_value, config.client_secret) assert_equal(developer_token_value, config.developer_token) + assert_equal(gaada_value, config.gaada) end def test_configure() @@ -48,18 +51,21 @@ def test_configure() client_id_value = 'abcd' client_secret_value = '!@#$' developer_token_value = '7x&Z' + gaada_value = 'gaada_value' config.configure do |c| c.refresh_token = refresh_token_value c.client_id = client_id_value c.client_secret = client_secret_value c.developer_token = developer_token_value + c.gaada = gaada_value end assert_equal(refresh_token_value, config.refresh_token) assert_equal(client_id_value, config.client_id) assert_equal(client_secret_value, config.client_secret) assert_equal(developer_token_value, config.developer_token) + assert_equal(gaada_value, config.gaada) end def test_use_cloud_org_for_api_access() diff --git a/test/test_metadata_interceptor.rb b/test/test_metadata_interceptor.rb index 27ccbe9e2..5d6a596a5 100644 --- a/test/test_metadata_interceptor.rb +++ b/test/test_metadata_interceptor.rb @@ -13,7 +13,8 @@ def setup "dev_token", "login_id", "linked_id", - false + false, + nil ) end @@ -71,7 +72,8 @@ def test_skips_developer_token_if_cloud_org "dev_token", "login_id", "linked_id", - true + true, + nil ) metadata = {} mi_cloud.request_response( @@ -83,4 +85,24 @@ def test_skips_developer_token_if_cloud_org end assert_nil metadata[:"developer-token"] end + + def test_appends_gaada_to_x_goog_api_client + mi_gaada = Google::Ads::GoogleAds::Interceptors::MetadataInterceptor.new( + "dev_token", + "login_id", + "linked_id", + false, + "1.2.3" + ) + metadata = { :"x-goog-api-client" => "gl-ruby/1.2.3" } + mi_gaada.request_response( + request: nil, + call: nil, + method: nil, + metadata: metadata + ) do + end + assert_includes metadata[:"x-goog-api-client"], "gaada/1.2.3" + assert_includes metadata[:"x-goog-api-client"], "pb/#{Gem.loaded_specs["google-protobuf"].version}" + end end