Skip to content

Commit a3089fc

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Add ProtoTypeMask class.
PiperOrigin-RevId: 818745535
1 parent 5eb221b commit a3089fc

File tree

4 files changed

+551
-0
lines changed

4 files changed

+551
-0
lines changed

checker/internal/BUILD

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,37 @@ cc_test(
271271
"@com_google_protobuf//:protobuf",
272272
],
273273
)
274+
275+
cc_library(
276+
name = "proto_type_mask",
277+
srcs = ["proto_type_mask.cc"],
278+
hdrs = ["proto_type_mask.h"],
279+
deps = [
280+
"//common:type",
281+
"//internal:status_macros",
282+
"//util/gtl:iterator_adaptors",
283+
"@com_google_absl//absl/container:flat_hash_map",
284+
"@com_google_absl//absl/memory",
285+
"@com_google_absl//absl/status",
286+
"@com_google_absl//absl/status:statusor",
287+
"@com_google_absl//absl/strings",
288+
"@com_google_absl//absl/strings:str_format",
289+
"@com_google_absl//absl/strings:string_view",
290+
"@com_google_absl//absl/types:optional",
291+
"@com_google_protobuf//:protobuf",
292+
],
293+
)
294+
295+
cc_test(
296+
name = "proto_type_mask_test",
297+
srcs = ["proto_type_mask_test.cc"],
298+
deps = [
299+
":proto_type_mask",
300+
"//internal:testing",
301+
"//internal:testing_descriptor_pool",
302+
"@com_google_absl//absl/container:flat_hash_map",
303+
"@com_google_absl//absl/status",
304+
"@com_google_absl//absl/status:status_matchers",
305+
"@com_google_absl//absl/strings:string_view",
306+
],
307+
)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2025 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+
// https://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+
#include "checker/internal/proto_type_mask.h"
16+
17+
#include <iterator>
18+
#include <memory>
19+
#include <set>
20+
#include <string>
21+
#include <vector>
22+
23+
#include "absl/container/flat_hash_map.h"
24+
#include "absl/memory/memory.h"
25+
#include "absl/status/status.h"
26+
#include "absl/status/statusor.h"
27+
#include "absl/strings/str_split.h"
28+
#include "absl/strings/string_view.h"
29+
#include "absl/strings/substitute.h"
30+
#include "absl/types/optional.h"
31+
#include "common/type.h"
32+
#include "internal/status_macros.h"
33+
#include "google/protobuf/descriptor.h"
34+
#include "util/gtl/iterator_adaptors.h"
35+
36+
using ::google::protobuf::Descriptor;
37+
using ::google::protobuf::DescriptorPool;
38+
using ::google::protobuf::FieldDescriptor;
39+
using TypeMap = absl::flat_hash_map<std::string, std::set<std::string>>;
40+
41+
absl::StatusOr<std::unique_ptr<ProtoTypeMask>> ProtoTypeMask::Create(
42+
const std::shared_ptr<const DescriptorPool>& descriptor_pool,
43+
const TypeMap& types_and_field_paths) {
44+
if (descriptor_pool == nullptr) {
45+
return absl::InvalidArgumentError(
46+
"ProtoTypeMask descriptor pool cannot be nullptr");
47+
}
48+
CEL_ASSIGN_OR_RETURN(
49+
auto types_and_visible_fields,
50+
ComputeVisibleFieldsMap(descriptor_pool, types_and_field_paths));
51+
std::unique_ptr<ProtoTypeMask> proto_type_mask =
52+
absl::WrapUnique(new ProtoTypeMask(types_and_visible_fields));
53+
return proto_type_mask;
54+
}
55+
56+
bool ProtoTypeMask::FieldIsVisible(absl::string_view type_name,
57+
absl::string_view field_name) {
58+
auto iterator = types_and_visible_fields_.find(type_name);
59+
if (iterator != types_and_visible_fields_.end() &&
60+
!iterator->second.contains(field_name.data())) {
61+
return false;
62+
}
63+
return true;
64+
}
65+
66+
absl::StatusOr<const Descriptor*> ProtoTypeMask::FindMessage(
67+
const std::shared_ptr<const DescriptorPool>& descriptor_pool,
68+
absl::string_view type_name) {
69+
const Descriptor* descriptor =
70+
descriptor_pool->FindMessageTypeByName(type_name);
71+
if (descriptor == nullptr) {
72+
return absl::InvalidArgumentError(absl::Substitute(
73+
"ProtoTypeMask type not found (type name: '$0')", type_name));
74+
}
75+
return descriptor;
76+
}
77+
78+
absl::StatusOr<const FieldDescriptor*> ProtoTypeMask::FindField(
79+
const Descriptor* descriptor, absl::string_view field_name) {
80+
const FieldDescriptor* field_descriptor =
81+
descriptor->FindFieldByName(field_name);
82+
if (field_descriptor == nullptr) {
83+
return absl::InvalidArgumentError(
84+
absl::Substitute("ProtoTypeMask could not select field from type "
85+
"(type name: '$0', field name: '$1')",
86+
descriptor->full_name(), field_name));
87+
}
88+
return field_descriptor;
89+
}
90+
91+
absl::StatusOr<const Descriptor*> ProtoTypeMask::GetMessage(
92+
const FieldDescriptor* field_descriptor) {
93+
cel::MessageTypeField field(field_descriptor);
94+
cel::Type type = field.GetType();
95+
absl::optional<cel::MessageType> message_type = type.AsMessage();
96+
if (!message_type.has_value()) {
97+
return absl::InvalidArgumentError(
98+
absl::Substitute("ProtoTypeMask subfield is not a message type "
99+
"(field name: '$0', type: '$1')",
100+
field_descriptor->name(), type.name()));
101+
}
102+
return &(*message_type.value());
103+
}
104+
105+
absl::Status ProtoTypeMask::AddAllHiddenFields(
106+
TypeMap& types_and_visible_fields, absl::string_view type_name) {
107+
auto result = types_and_visible_fields.find(type_name);
108+
if (result != types_and_visible_fields.end()) {
109+
if (!result->second.empty()) {
110+
return absl::InvalidArgumentError(absl::Substitute(
111+
"ProtoTypeMask cannot insert all hidden fields when the type has "
112+
"already been inserted with a visible field (type name: '$0')",
113+
type_name));
114+
}
115+
return absl::OkStatus();
116+
}
117+
types_and_visible_fields.insert({type_name.data(), {}});
118+
return absl::OkStatus();
119+
}
120+
121+
absl::Status ProtoTypeMask::AddVisibleField(TypeMap& types_and_visible_fields,
122+
absl::string_view type_name,
123+
absl::string_view field_name) {
124+
auto result = types_and_visible_fields.find(type_name);
125+
if (result != types_and_visible_fields.end()) {
126+
if (result->second.empty()) {
127+
return absl::InvalidArgumentError(
128+
absl::Substitute("ProtoTypeMask cannot insert a visible field when "
129+
"the type has already been "
130+
"inserted with all hidden fields (type name: "
131+
"'$0', field name: '$1')",
132+
type_name, field_name));
133+
}
134+
result->second.insert(field_name.data());
135+
return absl::OkStatus();
136+
}
137+
types_and_visible_fields.insert({type_name.data(), {field_name.data()}});
138+
return absl::OkStatus();
139+
}
140+
141+
absl::StatusOr<TypeMap> ProtoTypeMask::ComputeVisibleFieldsMap(
142+
const std::shared_ptr<const DescriptorPool>& descriptor_pool,
143+
const TypeMap& types_and_field_paths) {
144+
TypeMap types_and_visible_fields;
145+
for (absl::string_view type_name : gtl::key_view(types_and_field_paths)) {
146+
CEL_ASSIGN_OR_RETURN(const Descriptor* descriptor,
147+
FindMessage(descriptor_pool, type_name));
148+
std::set<std::string> field_paths =
149+
types_and_field_paths.find(type_name)->second;
150+
if (field_paths.empty()) {
151+
CEL_RETURN_IF_ERROR(AddAllHiddenFields(types_and_visible_fields,
152+
descriptor->full_name()));
153+
}
154+
for (absl::string_view field_path : field_paths) {
155+
std::vector<std::string> field_names = absl::StrSplit(field_path, '.');
156+
for (auto iterator = field_names.begin(); iterator != field_names.end();
157+
++iterator) {
158+
CEL_ASSIGN_OR_RETURN(const FieldDescriptor* field_descriptor,
159+
FindField(descriptor, *iterator));
160+
CEL_RETURN_IF_ERROR(AddVisibleField(
161+
types_and_visible_fields, descriptor->full_name(), *iterator));
162+
if (std::next(iterator) != field_names.end()) {
163+
CEL_ASSIGN_OR_RETURN(descriptor, GetMessage(field_descriptor));
164+
}
165+
}
166+
}
167+
}
168+
return types_and_visible_fields;
169+
}

checker/internal/proto_type_mask.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2025 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+
// https://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+
#ifndef THIRD_PARTY_CEL_CPP_CHECKER_PROTO_TYPE_MASK_H_
16+
#define THIRD_PARTY_CEL_CPP_CHECKER_PROTO_TYPE_MASK_H_
17+
18+
#include <memory>
19+
#include <set>
20+
#include <string>
21+
#include <utility>
22+
23+
#include "absl/container/flat_hash_map.h"
24+
#include "absl/status/status.h"
25+
#include "absl/status/statusor.h"
26+
#include "absl/strings/str_cat.h"
27+
#include "absl/strings/str_format.h"
28+
#include "absl/strings/str_join.h"
29+
#include "absl/strings/string_view.h"
30+
#include "google/protobuf/descriptor.h"
31+
#include "util/gtl/iterator_adaptors.h"
32+
33+
class ProtoTypeMask {
34+
public:
35+
static absl::StatusOr<std::unique_ptr<ProtoTypeMask>> Create(
36+
const std::shared_ptr<const google::protobuf::DescriptorPool>& descriptor_pool,
37+
const absl::flat_hash_map<std::string, std::set<std::string>>&
38+
types_and_field_paths);
39+
40+
absl::flat_hash_map<std::string, std::set<std::string>>
41+
GetTypesAndVisibleFields() {
42+
return types_and_visible_fields_;
43+
}
44+
45+
// Returns true when the field name is visible. A field is visible if:
46+
// 1. The type name is not a key in the map.
47+
// 2. The type name is a key in the map and the field name is in the set of
48+
// field names that are visible for the type.
49+
bool FieldIsVisible(absl::string_view type_name,
50+
absl::string_view field_name);
51+
52+
template <typename Sink>
53+
friend void AbslStringify(Sink& sink, const ProtoTypeMask& proto_type_mask) {
54+
std::string new_line = "\n";
55+
std::string output = absl::StrCat(new_line, "ProtoTypeMask {");
56+
auto types_and_visible_fields = proto_type_mask.types_and_visible_fields_;
57+
for (absl::string_view type_name :
58+
gtl::key_view(types_and_visible_fields)) {
59+
std::set<std::string> visible_fields =
60+
types_and_visible_fields.find(type_name)->second;
61+
absl::StrAppend(
62+
&output, new_line, "{", new_line, " type: ", type_name, new_line,
63+
" fields: ", absl::StrJoin(visible_fields, ", "), new_line, "}");
64+
}
65+
absl::StrAppend(&output, new_line, "}");
66+
absl::Format(&sink, "%v", output);
67+
}
68+
69+
private:
70+
static absl::StatusOr<const google::protobuf::Descriptor*> FindMessage(
71+
const std::shared_ptr<const google::protobuf::DescriptorPool>& descriptor_pool,
72+
absl::string_view type_name);
73+
74+
static absl::StatusOr<const google::protobuf::FieldDescriptor*> FindField(
75+
const google::protobuf::Descriptor* descriptor, absl::string_view field_name);
76+
77+
static absl::StatusOr<const google::protobuf::Descriptor*> GetMessage(
78+
const google::protobuf::FieldDescriptor* field_descriptor);
79+
80+
static absl::Status AddAllHiddenFields(
81+
absl::flat_hash_map<std::string, std::set<std::string>>&
82+
types_and_visible_fields,
83+
absl::string_view type_name);
84+
85+
static absl::Status AddVisibleField(
86+
absl::flat_hash_map<std::string, std::set<std::string>>&
87+
types_and_visible_fields,
88+
absl::string_view type_name, absl::string_view field_name);
89+
90+
static absl::StatusOr<absl::flat_hash_map<std::string, std::set<std::string>>>
91+
ComputeVisibleFieldsMap(
92+
const std::shared_ptr<const google::protobuf::DescriptorPool>& descriptor_pool,
93+
const absl::flat_hash_map<std::string, std::set<std::string>>&
94+
types_and_field_paths);
95+
96+
explicit ProtoTypeMask(absl::flat_hash_map<std::string, std::set<std::string>>
97+
types_and_visible_fields)
98+
: types_and_visible_fields_(std::move(types_and_visible_fields)) {};
99+
100+
// Map of types that have a field mask where the keys are
101+
// fully qualified type names and the values are the set of field names that
102+
// are visible for the type.
103+
absl::flat_hash_map<std::string, std::set<std::string>>
104+
types_and_visible_fields_;
105+
};
106+
107+
#endif // THIRD_PARTY_CEL_CPP_CHECKER_PROTO_TYPE_MASK_H_

0 commit comments

Comments
 (0)