Skip to content

Commit e4ff771

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Add ProtoTypeMaskRegistry, ProtoTypeMask, and FieldPath classes. The ProtoTypeMaskRegistry has functions that can be used to validate the input field masks and to check whether a field is visible.
PiperOrigin-RevId: 926925505
1 parent f26fd7d commit e4ff771

10 files changed

Lines changed: 1290 additions & 0 deletions

checker/internal/BUILD

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,95 @@ cc_test(
310310
"@com_google_absl//absl/types:optional",
311311
],
312312
)
313+
314+
cc_library(
315+
name = "field_path",
316+
srcs = ["field_path.cc"],
317+
hdrs = ["field_path.h"],
318+
deps = [
319+
"@com_google_absl//absl/strings",
320+
"@com_google_absl//absl/strings:string_view",
321+
"@com_google_absl//absl/types:span",
322+
],
323+
)
324+
325+
cc_test(
326+
name = "field_path_test",
327+
srcs = ["field_path_test.cc"],
328+
deps = [
329+
":field_path",
330+
"//internal:testing",
331+
"@com_google_absl//absl/strings",
332+
],
333+
)
334+
335+
cc_library(
336+
name = "proto_type_mask",
337+
srcs = ["proto_type_mask.cc"],
338+
hdrs = ["proto_type_mask.h"],
339+
deps = [
340+
":field_path",
341+
"//internal:status_macros",
342+
"@com_google_absl//absl/base:nullability",
343+
"@com_google_absl//absl/container:btree",
344+
"@com_google_absl//absl/status",
345+
"@com_google_absl//absl/status:statusor",
346+
"@com_google_absl//absl/strings",
347+
"@com_google_protobuf//:protobuf",
348+
],
349+
)
350+
351+
cc_test(
352+
name = "proto_type_mask_test",
353+
srcs = ["proto_type_mask_test.cc"],
354+
deps = [
355+
":field_path",
356+
":proto_type_mask",
357+
"//internal:testing",
358+
"//internal:testing_descriptor_pool",
359+
"@com_google_absl//absl/container:btree",
360+
"@com_google_absl//absl/status",
361+
"@com_google_absl//absl/strings",
362+
],
363+
)
364+
365+
cc_library(
366+
name = "proto_type_mask_registry",
367+
srcs = ["proto_type_mask_registry.cc"],
368+
hdrs = ["proto_type_mask_registry.h"],
369+
deps = [
370+
":field_path",
371+
":proto_type_mask",
372+
"//common:type",
373+
"//internal:status_macros",
374+
"@com_google_absl//absl/base:nullability",
375+
"@com_google_absl//absl/container:btree",
376+
"@com_google_absl//absl/container:flat_hash_map",
377+
"@com_google_absl//absl/container:flat_hash_set",
378+
"@com_google_absl//absl/memory",
379+
"@com_google_absl//absl/status",
380+
"@com_google_absl//absl/status:statusor",
381+
"@com_google_absl//absl/strings",
382+
"@com_google_absl//absl/strings:string_view",
383+
"@com_google_absl//absl/types:optional",
384+
"@com_google_absl//absl/types:span",
385+
"@com_google_protobuf//:protobuf",
386+
],
387+
)
388+
389+
cc_test(
390+
name = "proto_type_mask_registry_test",
391+
srcs = ["proto_type_mask_registry_test.cc"],
392+
deps = [
393+
":proto_type_mask",
394+
":proto_type_mask_registry",
395+
"//internal:testing",
396+
"//internal:testing_descriptor_pool",
397+
"@com_google_absl//absl/container:flat_hash_map",
398+
"@com_google_absl//absl/container:flat_hash_set",
399+
"@com_google_absl//absl/status",
400+
"@com_google_absl//absl/status:status_matchers",
401+
"@com_google_absl//absl/strings",
402+
"@com_google_absl//absl/strings:string_view",
403+
],
404+
)

checker/internal/field_path.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2026 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/field_path.h"
16+
17+
#include <string>
18+
19+
#include "absl/strings/str_join.h"
20+
#include "absl/strings/substitute.h"
21+
22+
namespace cel::checker_internal {
23+
24+
std::string FieldPath::DebugString() const {
25+
return absl::Substitute(
26+
"FieldPath { field path: '$0', field selection: {'$1'} }", path_,
27+
absl::StrJoin(field_selection_, "', '"));
28+
}
29+
30+
} // namespace cel::checker_internal

checker/internal/field_path.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2026 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_FIELD_PATH_H_
16+
#define THIRD_PARTY_CEL_CPP_CHECKER_FIELD_PATH_H_
17+
18+
#include <string>
19+
#include <utility>
20+
#include <vector>
21+
22+
#include "absl/strings/str_split.h"
23+
#include "absl/strings/string_view.h"
24+
#include "absl/types/span.h"
25+
26+
namespace cel::checker_internal {
27+
28+
// Represents a single path within a FieldMask.
29+
class FieldPath {
30+
public:
31+
explicit FieldPath(std::string path)
32+
: path_(std::move(path)),
33+
field_selection_(absl::StrSplit(path_, kPathDelimiter)) {}
34+
35+
// Returns the input path.
36+
// For example: "f.b.d".
37+
absl::string_view GetPath() const { return path_; }
38+
39+
// Returns the list of nested field names in the path.
40+
// For example: {"f", "b", "d"}.
41+
absl::Span<const std::string> GetFieldSelection() const {
42+
return field_selection_;
43+
}
44+
45+
// Returns the first field name in the path.
46+
// For example: "f".
47+
std::string GetFieldName() const { return field_selection_.front(); }
48+
49+
template <typename Sink>
50+
friend void AbslStringify(Sink& sink, const FieldPath& field_path) {
51+
sink.Append(field_path.DebugString());
52+
}
53+
54+
private:
55+
static constexpr char kPathDelimiter = '.';
56+
57+
std::string DebugString() const;
58+
59+
// The input path. For example: "f.b.d".
60+
std::string path_;
61+
// The list of nested field names in the path. For example: {"f", "b", "d"}.
62+
std::vector<std::string> field_selection_;
63+
};
64+
65+
inline bool operator==(const FieldPath& lhs, const FieldPath& rhs) {
66+
return lhs.GetFieldSelection() == rhs.GetFieldSelection();
67+
}
68+
69+
// Compares the field selections in the field paths.
70+
// This is only intended as an arbitrary ordering for a set.
71+
inline bool operator<(const FieldPath& lhs, const FieldPath& rhs) {
72+
return lhs.GetFieldSelection() < rhs.GetFieldSelection();
73+
}
74+
75+
} // namespace cel::checker_internal
76+
77+
#endif // THIRD_PARTY_CEL_CPP_CHECKER_FIELD_PATH_H_
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2026 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/field_path.h"
16+
17+
#include "absl/strings/str_cat.h"
18+
#include "internal/testing.h"
19+
20+
namespace cel::checker_internal {
21+
namespace {
22+
23+
using ::testing::ElementsAre;
24+
25+
TEST(FieldPathTest, EmptyPathReturnsEmptyString) {
26+
FieldPath field_path("");
27+
EXPECT_EQ(field_path.GetPath(), "");
28+
EXPECT_THAT(field_path.GetFieldSelection(), ElementsAre(""));
29+
EXPECT_EQ(field_path.GetFieldName(), "");
30+
}
31+
32+
TEST(FieldPathTest, DelimiterPathReturnsEmptyStrings) {
33+
FieldPath field_path(".");
34+
EXPECT_EQ(field_path.GetPath(), ".");
35+
EXPECT_THAT(field_path.GetFieldSelection(), ElementsAre("", ""));
36+
EXPECT_EQ(field_path.GetFieldName(), "");
37+
}
38+
39+
TEST(FieldPathTest, FieldPathReturnsFields) {
40+
FieldPath field_path("resource.name.other_field");
41+
EXPECT_EQ(field_path.GetPath(), "resource.name.other_field");
42+
EXPECT_THAT(field_path.GetFieldSelection(),
43+
ElementsAre("resource", "name", "other_field"));
44+
EXPECT_EQ(field_path.GetFieldName(), "resource");
45+
}
46+
47+
TEST(FieldPathTest, AbslStringifyPrintsFieldSelection) {
48+
FieldPath field_path("resource.name");
49+
EXPECT_EQ(absl::StrCat(field_path),
50+
"FieldPath { field path: 'resource.name', field selection: "
51+
"{'resource', 'name'} }");
52+
}
53+
54+
TEST(FieldPathTest, EqualsComparesFieldSelectionAndReturnsTrue) {
55+
FieldPath field_path_1("resource.name");
56+
FieldPath field_path_2("resource.name");
57+
EXPECT_TRUE(field_path_1 == field_path_2);
58+
}
59+
60+
TEST(FieldPathTest, EqualsComparesFieldSelectionAndReturnsFalse) {
61+
FieldPath field_path_1("resource.name");
62+
FieldPath field_path_2("resource.type");
63+
EXPECT_FALSE(field_path_1 == field_path_2);
64+
}
65+
66+
TEST(FieldPathTest, LessThanComparesFieldSelectionAndReturnsTrue) {
67+
FieldPath field_path_1("resource.name");
68+
FieldPath field_path_2("resource.type");
69+
EXPECT_TRUE(field_path_1 < field_path_2);
70+
}
71+
72+
TEST(FieldPathTest, LessThanComparesIdenticalFieldSelectionAndReturnsFalse) {
73+
FieldPath field_path_1("resource.name");
74+
FieldPath field_path_2("resource.name");
75+
EXPECT_FALSE(field_path_1 < field_path_2);
76+
}
77+
78+
TEST(FieldPathTest, LessThanComparesFieldSelectionAndReturnsFalse) {
79+
FieldPath field_path_1("resource.type");
80+
FieldPath field_path_2("resource.name");
81+
EXPECT_FALSE(field_path_1 < field_path_2);
82+
}
83+
84+
} // namespace
85+
} // namespace cel::checker_internal
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2026 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 <string>
18+
#include <vector>
19+
20+
#include "absl/base/nullability.h"
21+
#include "absl/container/btree_set.h"
22+
#include "absl/status/status.h"
23+
#include "absl/status/statusor.h"
24+
#include "absl/strings/str_join.h"
25+
#include "absl/strings/string_view.h"
26+
#include "absl/strings/substitute.h"
27+
#include "checker/internal/field_path.h"
28+
#include "internal/status_macros.h"
29+
#include "google/protobuf/descriptor.h"
30+
31+
namespace cel::checker_internal {
32+
33+
using ::google::protobuf::Descriptor;
34+
using ::google::protobuf::DescriptorPool;
35+
using ::google::protobuf::FieldDescriptor;
36+
37+
absl::StatusOr<const Descriptor*> FindMessage(
38+
const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
39+
absl::string_view type_name) {
40+
const Descriptor* descriptor =
41+
descriptor_pool->FindMessageTypeByName(type_name);
42+
if (descriptor == nullptr) {
43+
return absl::InvalidArgumentError(
44+
absl::Substitute("type '$0' not found", type_name));
45+
}
46+
return descriptor;
47+
}
48+
49+
absl::StatusOr<const FieldDescriptor*> FindField(const Descriptor* descriptor,
50+
absl::string_view field_name) {
51+
const FieldDescriptor* field_descriptor =
52+
descriptor->FindFieldByName(field_name);
53+
if (field_descriptor == nullptr) {
54+
return absl::InvalidArgumentError(
55+
absl::Substitute("could not select field '$0' from type '$1'",
56+
field_name, descriptor->full_name()));
57+
}
58+
return field_descriptor;
59+
}
60+
61+
absl::StatusOr<absl::btree_set<absl::string_view>> ProtoTypeMask::GetFieldNames(
62+
const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool) const {
63+
CEL_ASSIGN_OR_RETURN(const Descriptor* descriptor,
64+
FindMessage(descriptor_pool, this->GetTypeName()));
65+
absl::btree_set<absl::string_view> field_names;
66+
for (const FieldPath& field_path : this->GetFieldPaths()) {
67+
std::string field_name = field_path.GetFieldName();
68+
CEL_ASSIGN_OR_RETURN(const FieldDescriptor* field_descriptor,
69+
FindField(descriptor, field_name));
70+
field_names.insert(field_descriptor->name());
71+
}
72+
return field_names;
73+
}
74+
75+
std::string ProtoTypeMask::DebugString() const {
76+
// Represent each FieldPath by its path because it is easiest to read.
77+
std::vector<std::string> paths;
78+
paths.reserve(field_paths_.size());
79+
for (const FieldPath& field_path : field_paths_) {
80+
paths.emplace_back(field_path.GetPath());
81+
}
82+
return absl::Substitute(
83+
"ProtoTypeMask { type name: '$0', field paths: { '$1' } }", type_name_,
84+
absl::StrJoin(paths, "', '"));
85+
}
86+
87+
} // namespace cel::checker_internal

0 commit comments

Comments
 (0)