File Coverage

blib/lib/Google/Ads/GoogleAds/Utils/FieldMasks.pm
Criterion Covered Total %
statement 73 73 100.0
branch 26 28 92.8
condition 18 24 75.0
subroutine 14 14 100.0
pod 3 3 100.0
total 134 142 94.3


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             # Utility for constructing field masks, which are necessary for update
16             # operations.
17              
18              
19             use strict;
20 2     2   1653 use warnings;
  2         5  
  2         51  
21 2     2   8 use version;
  2         4  
  2         42  
22 2     2   9  
  2         5  
  2         9  
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 2     2   122 use Google::Ads::GoogleAds::Common::FieldMask;
  2         5  
  2         76  
27 2     2   802 use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
  2         6  
  2         43  
28 2     2   11  
  2         4  
  2         122  
29             use Exporter 'import';
30 2     2   10 our @EXPORT = qw(field_mask all_set_fields_of get_field_value);
  2         3  
  2         68  
31              
32             use Data::Compare;
33 2     2   777  
  2         20036  
  2         13  
34             # Compares two hash objects and computes a FieldMask object based on the differences
35             # between them. The field mask is necessary for update operations, and the field
36             # paths in the field mask are in lower underscore format.
37             my ($original, $modified) = @_;
38             my $paths = [];
39 27     27 1 9612 __compare($paths, "", $original, $modified);
40 27         36  
41 27         51 return Google::Ads::GoogleAds::Common::FieldMask->new({paths => $paths});
42             }
43 27         1372  
44             # Constructs a FieldMask object that captures the list of all set fields of an object.
45             # The field paths in the field mask are in lower underscore format.
46             my ($modified) = @_;
47             return field_mask({}, $modified);
48             }
49 5     5 1 21  
50 5         7 # Looks up the value of the field located at the given path on an object.
51             my ($object, $path) = @_;
52              
53             my $value = $object;
54             $value = $value->{$_} for split(/\./, $path);
55 4     4 1 385  
56             return $value;
57 4         6 }
58 4         13  
59             # The private method to compare a given field for two objects, and capture the
60 4         15 # differences between them recursively.
61             my ($paths, $current_field, $original, $modified) = @_;
62              
63             # Combine fields from original and modified resource, since the original might
64             # have nested fields which are cleared in the modified resource.
65             my %key_hash = ();
66 47     47   73 foreach my $key (keys %$original) { $key_hash{$key} = 1; }
67             foreach my $key (keys %$modified) { $key_hash{$key} = 1; }
68              
69             foreach my $key (keys %key_hash) {
70 47         72 # The field mask should contain the field path in underscore format.
71 47         111 my $field = to_lower_underscore($key);
  25         37  
72 47         83 my $field_path = $current_field ? $current_field . "." . $field : $field;
  64         86  
73              
74 47         66 # Extract values from original and modified objects.
75             my $modified_value = $modified->{$key};
76 68         927 my $original_value = $original->{$key};
77 68 100       132 my $original_key_exists = exists $original->{$key};
78              
79             if (ref($original_value) eq "ARRAY" || ref($modified_value) eq "ARRAY") {
80 68         84 # Array reference field.
81 68         71 push @$paths, $field_path
82 68         74 unless Compare($original_value, $modified_value);
83             } elsif (__is_hash_ref($original_value) || __is_hash_ref($modified_value)) {
84 68 100 100     220 # Hash or class reference field whose ref name is not empty.
    100 100        
85             next if Compare($original_value, $modified_value);
86 8 100       17 if (!$original_key_exists) {
87             # If the modified value is an empty object that doesn't exist in the original
88             # then add it to the paths list. Otherwise recurse on the modified value object.
89             if (!%$modified_value) { push @$paths, $field_path; }
90 27 50       53 else {
91 27 100       1251 __compare($paths, $field_path, {}, $modified_value);
    100          
92             }
93             } elsif (__is_clearing_message($original_value, $modified_value)) {
94 20 100       44 push @$paths, $field_path;
  6         16  
95             } else {
96 14         25 if (!defined $modified_value) {
97             __compare($paths, $field_path, $original_value, {});
98             } else {
99 1         3 __compare($paths, $field_path, $original_value, $modified_value);
100             }
101 6 100       11 }
102 1         2 } else {
103             # Scalar field or both $modified_value and $original_value are undef.
104 5         10 if (!$original_key_exists && !defined $modified_value) {
105             push @$paths, $field_path;
106             next;
107             }
108              
109 33 100 100     79 push @$paths, $field_path
110 1         2 unless Compare($original_value, $modified_value);
111 1         2 }
112             }
113             }
114 32 100       53  
115             # The private method to check if a reference object is for a hash or a class instance.
116             my $ref_type = ref shift;
117             return 0 if !$ref_type;
118              
119             # A boolean value reference in a JSON object is evaluated as "JSON::PP::Boolean".
120             my @invalid_types = ("SCALAR", "ARRAY", "JSON::PP::Boolean");
121             return 0 if grep /^$ref_type/, @invalid_types;
122 113     113   128  
123 113 100       219 return 1;
124             }
125              
126 30         50 my ($original_value, $modified_value) = @_;
127 30 100       221  
128             my $original_is_defined = defined $original_value;
129 27         64 my $modified_is_defined = defined $modified_value;
130              
131             my $original_values_size = values %$original_value;
132             my $modified_values_size = values %$modified_value;
133 7     7   20  
134             # Returns true if the original message contains an empty message field that is not present on the
135 7         10 # modified message, or vice-versa, in which case the user is attempting to clear the top level
136 7         8 # message field.
137             return 1
138 7         9 if ((
139 7         10 !$modified_is_defined
140             and $original_is_defined
141             and $original_values_size == 0
142             )
143             or ( !$original_is_defined
144 7 50 66     37 and $modified_is_defined
      100        
      33        
      33        
      66        
145             and $modified_values_size == 0));
146              
147             return 0;
148             }
149              
150             1;
151              
152             =pod
153              
154 6         12 =head1 NAME
155              
156             Google::Ads::GoogleAds::Utils::FieldMasks
157              
158             =head1 DESCRIPTION
159              
160             Utility for constructing field masks, which are necessary for update operations.
161              
162             =head1 METHODS
163              
164             =head2 field_mask
165              
166             Compares two hash objects and computes a L<Google::Ads::GoogleAds::Common::FieldMask>
167             object based on the differences between them. The field mask is necessary for
168             update operations, and the field paths in the field mask are in lower underscore format.
169              
170             =head3 Parameters
171              
172             =over
173              
174             =item *
175              
176             I<original>: the original hash object.
177              
178             =item *
179              
180             I<modified>: the modified hash object.
181              
182             =back
183              
184             =head3 Returns
185              
186             A L<Google::Ads::GoogleAds::Common::FieldMask> object reflecting the changes
187             between the original and modified objects.
188              
189             =head2 all_set_fields_of
190              
191             Constructs a L<Google::Ads::GoogleAds::Common::FieldMask> object that captures
192             the list of all set fields of an object. The field paths in the field mask are
193             in lower underscore format.
194              
195             =head3 Parameters
196              
197             =over
198              
199             =item *
200              
201             I<modified>: the modified hash object.
202              
203             =back
204              
205             =head3 Returns
206              
207             A L<Google::Ads::GoogleAds::Common::FieldMask> object that captures the list of
208             all set fields of an object.
209              
210             =head2 get_field_value
211              
212             Looks up the value of the field located at the given path on an object.
213              
214             =head3 Parameters
215              
216             =over
217              
218             =item *
219              
220             I<object>: the object to search on.
221              
222             =item *
223              
224             I<path>: the path of the field.
225              
226             =back
227              
228             =head3 Returns
229              
230             The value of the field located at the give path on the object.
231              
232             =head2 __compare
233              
234             The private method to compare a given field for two objects, and capture the
235             differences between them recursively.
236              
237             =head3 Parameters
238              
239             =over
240              
241             =item *
242              
243             I<paths>: the paths array to store the differences.
244              
245             =item *
246              
247             I<current_field>: the field name to compare.
248              
249             =item *
250              
251             I<original>: the original hash object.
252              
253             =item *
254              
255             I<modified>: the modified hash object.
256              
257             =back
258              
259             =head2 __is_hash_ref
260              
261             The private method to check if a reference object is for a hash or a class instance.
262              
263             =head3 Parameters
264              
265             =over
266              
267             =item *
268              
269             I<ref>: the reference object to check.
270              
271             =back
272              
273             =head3 Returns
274              
275             True, if the reference object is for a hash or a class instance. False, otherwise.
276              
277             =head1 LICENSE AND COPYRIGHT
278              
279             Copyright 2019 Google LLC
280              
281             Licensed under the Apache License, Version 2.0 (the "License");
282             you may not use this file except in compliance with the License.
283             You may obtain a copy of the License at
284              
285             http://www.apache.org/licenses/LICENSE-2.0
286              
287             Unless required by applicable law or agreed to in writing, software
288             distributed under the License is distributed on an "AS IS" BASIS,
289             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
290             See the License for the specific language governing permissions and
291             limitations under the License.
292              
293             =head1 REPOSITORY INFORMATION
294              
295             $Rev: $
296             $LastChangedBy: $
297             $Id: $
298              
299             =cut