File Coverage

blib/lib/Google/Ads/GoogleAds/BaseService.pm
Criterion Covered Total %
statement 96 107 89.7
branch 19 46 41.3
condition 3 9 33.3
subroutine 18 18 100.0
pod 1 2 50.0
total 137 182 75.2


line stmt bran cond sub pod time code
1             # Copyright 2019, Google LLC
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14             #
15             # The base class for all Google Ads API services, e.g. CampaignService,
16             # AdGroupService, etc.
17              
18              
19             use strict;
20 4     4   2052 use warnings;
  4         7  
  4         118  
21 4     4   22 use version;
  4         9  
  4         96  
22 4     4   20  
  4         11  
  4         29  
23             # The following needs to be on one line because CPAN uses a particularly hacky
24             # eval() to determine module versions.
25             use Google::Ads::GoogleAds::Constants; our $VERSION = ${Google::Ads::GoogleAds::Constants::VERSION};
26 4     4   256 use Google::Ads::GoogleAds::Logging::GoogleAdsLogger;
  4         8  
  4         186  
27 4     4   34 use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
  4         9  
  4         85  
28 4     4   1453 use Google::Ads::GoogleAds::GoogleAdsException;
  4         8  
  4         247  
29 4     4   1371  
  4         11  
  4         117  
30             use Class::Std::Fast;
31 4     4   22 use LWP::UserAgent::Determined;
  4         6  
  4         17  
32 4     4   2085 use HTTP::Status qw(:constants);
  4         1824  
  4         106  
33 4     4   23 use JSON::XS;
  4         7  
  4         2117  
34 4     4   26 use URI::Query;
  4         7  
  4         225  
35 4     4   1787 use utf8;
  4         7967  
  4         181  
36 4     4   2110  
  4         52  
  4         19  
37             use constant GET => "GET";
38 4     4   118 use constant POST => "POST";
  4         8  
  4         274  
39 4     4   22  
  4         16  
  4         3308  
40             # Class::Std-style attributes. Need to be kept in the same line.
41             # These need to go in the same line for older Perl interpreters to understand.
42             my %api_client_of : ATTR(:name<api_client> :default<>);
43             my %__lwp_agent_of : ATTR(:name<__lwp_agent> :default<>);
44             my %__json_coder_of : ATTR(:name<__json_coder> :default<>);
45              
46             # Automatically called by Class::Std after the values for all the attributes
47             # have been populated but before the constructor returns the new object.
48             my ($self, $ident) = @_;
49              
50 4     4 0 1056 $__lwp_agent_of{$ident} ||= LWP::UserAgent::Determined->new();
51             # The 'pretty' attribute should be enabled for more readable form in the log.
52 4   66     34 # The 'convert_blessed' attributed should be enabled to convert blessed objects.
53             $__json_coder_of{$ident} ||= JSON::XS->new->utf8->pretty->convert_blessed;
54             }
55 4   33     795  
56             # Sends a HTTP request to Google Ads API server and handles the response.
57             my ($self, $http_method, $request_path, $request_body, $response_type,
58             $content_callback)
59             = @_;
60 1     1 1 11  
61             my $api_client = $self->get_api_client();
62              
63             ##############################################################################
64 1         5 # Step 1: Prepare for the request URL and request content.
65             ##############################################################################
66             if ($http_method eq GET) {
67             # HTTP GET request scenarios:
68             # GET: v11/customers:listAccessibleCustomers
69 1 50       12 # GET: v11/{+resourceName}
    50          
70             # GET: v11/{+resourceName}:listResults
71             # GET: v11/customers/{+customerId}/paymentsAccounts
72             # GET: v11/customers/{+customerId}/merchantCenterLinks
73             $request_path = expand_path_template($request_path, $request_body);
74              
75             # GET: When the $request_body is a hash reference, use the path parameters
76 0         0 # in the hash to expand the $request_path, and add all the other key-value
77             # pairs to the URL query parameters if there is any. e.g.
78             #
79             # GET: CampaignExperimentService.list_async_errors
80             # GET: CampaignDraftService.list_async_errors
81             # GET: BatchJobService.list_results
82             if (ref $request_body and (keys %$request_body) > 0) {
83             $request_path .= ("?" . URI::Query->new($request_body));
84             }
85 0 0 0     0 } elsif ($http_method eq POST) {
86 0         0 # HTTP POST request scenarios:
87             # POST: v11/geoTargetConstants:suggest
88             # POST: v11/googleAdsFields:search
89             # POST: v11/customers/{+customerId}/googleAds:search
90             # POST: v11/customers/{+customerId}/campaigns:mutate
91             # POST: v11/{+keywordPlan}:generateForecastMetrics
92             # POST: v11/{+campaignDraft}:promote
93             # POST: v11/{+resourceName}:addOperations
94              
95             # POST: Retain the 'customerId' variable in the $request_body hash
96             # reference after the $request_path is expanded.
97             my $customer_id = $request_body->{customerId} if ref $request_body;
98              
99             $request_path = expand_path_template($request_path, $request_body);
100 1 50       8  
101             $request_body->{customerId} = $customer_id if defined $customer_id;
102 1         4 } else {
103             # Other HTTP request scenarios:
104 1 50       4 # DELETE: v11/{+name} for OperationService
105             $request_path = expand_path_template($request_path, $request_body);
106             }
107              
108 0         0 # Generate the request URL from the API service address and the request path.
109             my $request_url = $api_client->get_service_address() . $request_path;
110              
111             my $json_coder = $self->get___json_coder();
112 1         5  
113             # Encode the JSON request content for POST request.
114 1         14 my $request_content = undef;
115             if ($http_method eq POST) {
116             $request_content =
117 1         4 defined $request_body
118 1 50       4 ? $json_coder->encode($request_body)
119 1 50       9 : '{}';
120             }
121              
122             ##############################################################################
123             # Step 2: Send the authorized HTTP request and handle the HTTP response.
124             ##############################################################################
125             my $auth_handler = $api_client->_get_auth_handler();
126             if (!$auth_handler) {
127             $api_client->get_die_on_faults()
128 1         3 ? die(Google::Ads::GoogleAds::Constants::NO_AUTH_HANDLER_SETUP_MESSAGE)
129 1 50       76 : warn(Google::Ads::GoogleAds::Constants::NO_AUTH_HANDLER_SETUP_MESSAGE);
130 0 0       0 return;
131             }
132              
133 0         0 my $http_headers = $self->_get_http_headers();
134             my $http_request =
135             $auth_handler->prepare_request($http_method, $request_url, $http_headers,
136 1         5 $request_content);
137 1         8  
138             utf8::is_utf8 $http_request and utf8::encode $http_request;
139              
140             my $lwp_agent = $self->get___lwp_agent();
141 1 50       141  
142             # Set up HTTP timeout, retry and proxy for the lwp agent.
143 1         5 my $http_timeout = $api_client->get_http_timeout();
144             $lwp_agent->timeout(
145             $http_timeout
146 1         9 ? $http_timeout
147 1 50       9 : Google::Ads::GoogleAds::Constants::DEFAULT_HTTP_TIMEOUT
148             );
149              
150             my $http_retry_timing = $api_client->get_http_retry_timing();
151             $lwp_agent->timing(
152             $http_retry_timing
153 1         55 ? $http_retry_timing
154 1 50       10 : Google::Ads::GoogleAds::Constants::DEFAULT_HTTP_RETRY_TIMING
155             );
156             # Retry for status codes 503 & 504 to be parity with gRPC.
157             $lwp_agent->codes_to_determinate(
158             {HTTP_SERVICE_UNAVAILABLE => 1, HTTP_GATEWAY_TIMEOUT => 1});
159              
160 1         349 my $proxy = $api_client->get_proxy();
161             $proxy
162             ? $lwp_agent->proxy(['http', 'https'], $proxy)
163 1         198 : $lwp_agent->env_proxy;
164 1 50       12  
165             # Keep track of the last sent HTTP request.
166             $api_client->set_last_request($http_request);
167              
168             # Send HTTP request with optional content callback handler (for search stream).
169 1         62 my $http_response = undef;
170             if ($content_callback) {
171             $http_response = $lwp_agent->request($http_request, $content_callback);
172 1         6 # The callback handler is not invoked when the response status is error.
173 1 50       2 if ($http_response->is_error) {
174 1         6 # The error response content returned by the search stream interface is
175             # enclosed in square brackets, deviating from normal GoogleAdsException.
176 1 50       1025 # Remove the leading and trailing square brackets in the response content,
177             # to make it operable in the subsequent steps (logging, exception handling).
178             $http_response->content($http_response->decoded_content =~ s/^\[|\]$//gr);
179             }
180             } else {
181 1         21 $http_response = $lwp_agent->request($http_request);
182             }
183              
184 0         0 # Keep track of the last received HTTP response.
185             $api_client->set_last_response($http_response);
186              
187             my $response_content = $http_response->decoded_content();
188 1         500  
189             ##############################################################################
190 1         7 # Step 3: Log the one-line summary and the traffic detail. Error may occur
191             # when the response content is not in JSON format.
192             ##############################################################################
193             eval {
194             Google::Ads::GoogleAds::Logging::GoogleAdsLogger::log_summary($http_request,
195             $http_response);
196 1         86 Google::Ads::GoogleAds::Logging::GoogleAdsLogger::log_detail($http_request,
197 1         4 $http_response);
198             };
199 1         37 if ($@) {
200             $api_client->get_die_on_faults()
201             ? die($response_content . "\n")
202 1 50       25 : warn($response_content . "\n");
203 0 0       0 return;
204             }
205              
206 0         0 ##############################################################################
207             # Step 4: Return the decoded object or exception from the response content.
208             ##############################################################################
209             my $response_body =
210             $response_content ? $json_coder->decode($response_content) : {};
211              
212 1 50       14 if ($http_response->is_success) {
213             # Bless the JSON format response to the response type class.
214             bless $response_body, $response_type if $response_type;
215 1 50       2 return $response_body;
216             } else {
217 0 0       0 $api_client->get_die_on_faults()
218 0         0 ? die_with_code(1, $response_content)
219             : warn($response_content);
220 1 50       10  
221             return Google::Ads::GoogleAds::GoogleAdsException->new($response_body);
222             }
223             }
224 1         21  
225             # Protected method to generate the appropriate REST request headers.
226             my $self = shift;
227              
228             my $api_client = $self->get_api_client();
229              
230 1     1   2 my $headers = [
231             "Content-Type",
232 1         3 "application/json; charset=utf-8",
233             "user-agent",
234 1         9 $api_client->get_user_agent(),
235             "x-goog-api-client",
236             join(' ',
237             Google::Ads::GoogleAds::Constants::DEFAULT_USER_AGENT,
238             "gccl/" . Google::Ads::GoogleAds::BaseService->VERSION,
239             "rest/" . $LWP::UserAgent::Determined::VERSION),
240             "developer-token",
241             $api_client->get_developer_token()];
242              
243             my $login_customer_id = $api_client->get_login_customer_id();
244             push @$headers, ("login-customer-id", $login_customer_id =~ s/-//gr)
245             if $login_customer_id;
246              
247 1         20 my $linked_customer_id = $api_client->get_linked_customer_id();
248 1 50       9 push @$headers, ("linked-customer-id", $linked_customer_id =~ s/-//gr)
249             if $linked_customer_id;
250              
251 1         4 return $headers;
252 1 50       8 }
253              
254             1;
255 1         2  
256             =pod
257              
258             =head1 NAME
259              
260             Google::Ads::GoogleAds::BaseService
261              
262             =head1 DESCRIPTION
263              
264             The abstract base class for all Google Ads API services, e.g. CampaignService,
265             AdGroupService, etc.
266              
267             =head1 SYNOPSIS
268              
269             use Google::Ads::GoogleAds::Client;
270              
271             my $api_client = Google::Ads::GoogleAds::Client->new();
272              
273             my $campaign_service = $api_client->CampaignService();
274              
275             =head1 ATTRIBUTES
276              
277             Each service instance is initialized by L<Google::Ads::GoogleAds::Client>, and
278             these attributes are set automatically.
279              
280             Alternatively, there is a get_ and set_ method associated with each attribute
281             for retrieving or setting them dynamically.
282              
283             my %api_client_of : ATTR(:name<api_client> :default<>);
284              
285             =head2 api_client
286              
287             A reference to the L<Google::Ads::GoogleAds::Client>, holding the API credentials
288             and configurations.
289              
290             =head1 METHODS
291              
292             =head2 call
293              
294             Sends REST HTTP requests to Google Ads API server and handles the responses.
295              
296             =head3 Parameters
297              
298             =over
299              
300             =item *
301              
302             I<http_method>: The HTTP request method, e.g. GET, POST.
303              
304             =item *
305              
306             I<request_path>: The relative request URL which may contain wildcards to expand,
307             e.g. {+resourceName}, {+customerId}.
308              
309             =item *
310              
311             I<request_body>: A Perl object representing the HTTP request payload, which will
312             be used to expand the {+resourceName} or any other expression in the request path
313             and encoded into JSON string for a HTTP POST request.
314              
315             =item *
316              
317             I<response_type>: The class name of the expected response. An instance of this class
318             will be returned if the request succeeds.
319              
320             =item *
321              
322             I<content_callback>: The optional streaming content callback method.
323              
324             =back
325              
326             =head3 Returns
327              
328             An instance of the class defined by the C<response_type> parameter, or a
329             L<Google::Ads::GoogleAds::GoogleAdsException> object if an error has occurred at
330             the server side by default. However if the C<die_on_faults> flag is set to true
331             in L<Google::Ads::GoogleAds::Client>, the service will issue a die() with error
332             message on API errors.
333              
334             =head2 _get_http_headers
335              
336             Prepare the basic HTTP request headers including Content-Type, user-agent,
337             developer-token, login-customer-id, linked-customer-id - if needed. The headers
338             will be consolidated with access token in the method of
339             L<Google::Ads::GoogleAds::Common::OAuth2BaseHandler/prepare_request>.
340              
341             =head3 Returns
342              
343             The basic HTTP headers including Content-Type, user-agent, developer-token,
344             login-customer-id, linked-customer-id - if needed.
345              
346             =head1 LICENSE AND COPYRIGHT
347              
348             Copyright 2019 Google LLC
349              
350             Licensed under the Apache License, Version 2.0 (the "License");
351             you may not use this file except in compliance with the License.
352             You may obtain a copy of the License at
353              
354             http://www.apache.org/licenses/LICENSE-2.0
355              
356             Unless required by applicable law or agreed to in writing, software
357             distributed under the License is distributed on an "AS IS" BASIS,
358             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
359             See the License for the specific language governing permissions and
360             limitations under the License.
361              
362             =head1 REPOSITORY INFORMATION
363              
364             $Rev: $
365             $LastChangedBy: $
366             $Id: $
367              
368             =cut