Skip to content

Commit f62f929

Browse files
authored
[ProtoApiScrubber] Add support for enum types (#42199)
Signed-off-by: Sumit Kumar <[email protected]>
1 parent 20df9b5 commit f62f929

File tree

7 files changed

+344
-3
lines changed

7 files changed

+344
-3
lines changed

source/extensions/filters/http/proto_api_scrubber/filter_config.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ absl::Status ProtoApiScrubberFilterConfig::initializeMethodFieldRestrictions(
270270
return absl::OkStatus();
271271
}
272272

273+
absl::StatusOr<absl::string_view>
274+
ProtoApiScrubberFilterConfig::getEnumName(absl::string_view enum_type_name, int enum_value) const {
275+
const auto* enum_desc = descriptor_pool_->FindEnumTypeByName(std::string(enum_type_name));
276+
if (enum_desc == nullptr) {
277+
return absl::NotFoundError(
278+
absl::StrCat("Enum type '", enum_type_name, "' not found in descriptor pool."));
279+
}
280+
281+
const auto* enum_value_desc = enum_desc->FindValueByNumber(enum_value);
282+
if (enum_value_desc == nullptr) {
283+
return absl::NotFoundError(absl::StrCat("Enum value '", enum_value,
284+
"' not found in enum type '", enum_type_name, "'."));
285+
}
286+
287+
return enum_value_desc->name();
288+
}
289+
273290
MatchTreeHttpMatchingDataSharedPtr
274291
ProtoApiScrubberFilterConfig::getRequestFieldMatcher(const std::string& method_name,
275292
const std::string& field_mask) const {

source/extensions/filters/http/proto_api_scrubber/filter_config.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ class ProtoApiScrubberFilterConfig : public Logger::Loggable<Logger::Id::filter>
107107
virtual MatchTreeHttpMatchingDataSharedPtr
108108
getMessageMatcher(const std::string& message_name) const;
109109

110+
/**
111+
* Resolves the human-readable name of a specific enum value.
112+
*
113+
* @param enum_type_name The fully qualified name of the enum type (e.g., "package.Status").
114+
* @param enum_value The integer value of the enum (e.g., 99).
115+
* @return The string name of the enum value (e.g., "DEBUG_MODE").
116+
* Returns empty string if the type or value is not found.
117+
*/
118+
virtual absl::StatusOr<absl::string_view> getEnumName(absl::string_view enum_type_name,
119+
int enum_value) const;
120+
110121
// Returns a constant reference to the type finder which resolves type URL string to the
111122
// corresponding `Protobuf::Type*`.
112123
const TypeFinder& getTypeFinder() const { return *type_finder_; };

source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ envoy_cc_library(
1818
],
1919
deps = [
2020
"//source/common/common:minimal_logger_lib",
21+
"//source/common/grpc:common_lib",
2122
"//source/common/http/matching:data_impl_lib",
2223
"//source/common/protobuf",
2324
"//source/extensions/filters/http/proto_api_scrubber:filter_config",

source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.cc

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#include "source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h"
22

3+
#include "source/common/grpc/common.h"
34
#include "source/common/http/matching/data_impl.h"
45
#include "source/common/protobuf/protobuf.h"
6+
#include "source/common/protobuf/utility.h"
57
#include "source/extensions/filters/http/proto_api_scrubber/filter_config.h"
68

9+
#include "absl/strings/numbers.h"
10+
#include "absl/strings/str_cat.h"
711
#include "absl/strings/str_join.h"
812
#include "proto_processing_lib/proto_scrubber/field_checker_interface.h"
913

@@ -12,9 +16,56 @@ namespace Extensions {
1216
namespace HttpFilters {
1317
namespace ProtoApiScrubber {
1418

19+
absl::StatusOr<absl::string_view>
20+
FieldChecker::resolveEnumName(absl::string_view value_str, const Protobuf::Field* field) const {
21+
int enum_number;
22+
if (!absl::SimpleAtoi(value_str, &enum_number)) {
23+
return absl::InvalidArgumentError(
24+
absl::StrCat("Enum value '", value_str, "' is not a valid integer."));
25+
}
26+
27+
// Extract Type Name from URL "type.googleapis.com/package.Name"
28+
absl::string_view type_name = Envoy::TypeUtil::typeUrlToDescriptorFullName(field->type_url());
29+
30+
// Return the corresponding enum name.
31+
return filter_config_ptr_->getEnumName(type_name, enum_number);
32+
}
33+
34+
std::string FieldChecker::constructFieldMask(const std::vector<std::string>& path,
35+
const Protobuf::Field* field) const {
36+
if (path.empty()) {
37+
return "";
38+
}
39+
40+
// Translate the last segment of the `path` wherever required.
41+
absl::string_view last_segment = path.back();
42+
switch (field->kind()) {
43+
case Protobuf::Field::TYPE_ENUM: {
44+
if (auto name_or_status = resolveEnumName(last_segment, field);
45+
name_or_status.ok() && !name_or_status.value().empty()) {
46+
last_segment = name_or_status.value();
47+
} else {
48+
ENVOY_LOG(warn, "Enum translation skipped for value '{}': {}", last_segment,
49+
name_or_status.status().ToString());
50+
}
51+
} break;
52+
53+
default:
54+
break;
55+
}
56+
57+
// If path has only 1 segment, just return it (translated or original).
58+
if (path.size() == 1) {
59+
return std::string(last_segment);
60+
}
61+
62+
// Join all segments except the last one, then append the (potentially translated) last segment.
63+
return absl::StrCat(absl::StrJoin(path.begin(), path.end() - 1, "."), ".", last_segment);
64+
}
65+
1566
FieldCheckResults FieldChecker::CheckField(const std::vector<std::string>& path,
1667
const Protobuf::Field* field) const {
17-
const std::string field_mask = absl::StrJoin(path, ".");
68+
const std::string field_mask = constructFieldMask(path, field);
1869

1970
MatchTreeHttpMatchingDataSharedPtr match_tree;
2071

@@ -58,7 +109,7 @@ FieldCheckResults FieldChecker::CheckField(const std::vector<std::string>& path,
58109
}
59110

60111
FieldCheckResults FieldChecker::matchResultStatusToFieldCheckResult(
61-
absl::StatusOr<Matcher::MatchResult>& match_result, const std::string& field_mask) const {
112+
absl::StatusOr<Matcher::MatchResult>& match_result, absl::string_view field_mask) const {
62113
// Preserve the field (i.e., kInclude) if there's any error in evaluating the match.
63114
// This can happen in two cases:
64115
// 1. The match tree is corrupt.

source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,17 @@ class FieldChecker : public FieldCheckerInterface, public Logger::Loggable<Logge
7979

8080
FieldCheckResults
8181
matchResultStatusToFieldCheckResult(absl::StatusOr<Matcher::MatchResult>& match_result,
82-
const std::string& field_mask) const;
82+
absl::string_view field_mask) const;
83+
84+
// Resolves the string name of an Enum value.
85+
absl::StatusOr<absl::string_view> resolveEnumName(absl::string_view value_str,
86+
const Protobuf::Field* field) const;
87+
88+
// Constructs the field mask, handling translations for different data types.
89+
// Currently, it only handles enum data type. Support for protobuf maps and `Any` types will be
90+
// added in the future.
91+
std::string constructFieldMask(const std::vector<std::string>& path,
92+
const Protobuf::Field* field) const;
8393

8494
ScrubberContext scrubber_context_;
8595
Http::Matching::HttpMatchingDataImpl matching_data_;

test/extensions/filters/http/proto_api_scrubber/filter_config_test.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,33 @@ class ProtoApiScrubberFilterConfigTest : public ::testing::Test {
459459
return proto_config;
460460
}
461461

462+
// Helper to create a serialized FileDescriptorSet containing a specific Enum.
463+
// Defines: enum test.TestEnum { UNKNOWN = 0; ACTIVE = 1; }
464+
std::string createDescriptorWithTestEnum() {
465+
Protobuf::FileDescriptorProto file_proto;
466+
file_proto.set_name("test_enum.proto");
467+
file_proto.set_package("test");
468+
file_proto.set_syntax("proto3");
469+
470+
auto* enum_type = file_proto.add_enum_type();
471+
enum_type->set_name("TestEnum");
472+
473+
auto* val0 = enum_type->add_value();
474+
val0->set_name("UNKNOWN");
475+
val0->set_number(0);
476+
477+
auto* val1 = enum_type->add_value();
478+
val1->set_name("ACTIVE");
479+
val1->set_number(1);
480+
481+
Envoy::Protobuf::FileDescriptorSet descriptor_set;
482+
descriptor_set.add_file()->CopyFrom(file_proto);
483+
484+
std::string descriptor_bytes;
485+
descriptor_set.SerializeToString(&descriptor_bytes);
486+
return descriptor_bytes;
487+
}
488+
462489
Api::ApiPtr api_;
463490
ProtoApiScrubberConfig proto_config_;
464491
std::shared_ptr<const ProtoApiScrubberFilterConfig> filter_config_;
@@ -989,6 +1016,49 @@ TEST_F(ProtoApiScrubberFilterConfigTest, GetResponseType) {
9891016
}
9901017
}
9911018

1019+
TEST_F(ProtoApiScrubberFilterConfigTest, GetEnumName) {
1020+
// Setup Config with the custom Enum descriptor
1021+
ProtoApiScrubberConfig config;
1022+
*config.mutable_descriptor_set()->mutable_data_source()->mutable_inline_bytes() =
1023+
createDescriptorWithTestEnum();
1024+
1025+
// Initialize filter config.
1026+
auto filter_config_or_status = ProtoApiScrubberFilterConfig::create(config, factory_context_);
1027+
ASSERT_THAT(filter_config_or_status, IsOk());
1028+
auto filter_config = filter_config_or_status.value();
1029+
1030+
{
1031+
// Case 1.1: Valid Lookup (Type and Value exist)
1032+
auto result = filter_config->getEnumName("test.TestEnum", 1);
1033+
ASSERT_THAT(result, IsOk());
1034+
EXPECT_EQ(result.value(), "ACTIVE");
1035+
}
1036+
1037+
{
1038+
// Case 1.2: Valid Lookup (Type and Value exist)
1039+
auto result = filter_config->getEnumName("test.TestEnum", 0);
1040+
ASSERT_THAT(result, IsOk());
1041+
EXPECT_EQ(result.value(), "UNKNOWN");
1042+
}
1043+
1044+
{
1045+
// Case 2: Invalid Value (Type exists, Value does not)
1046+
auto result = filter_config->getEnumName("test.TestEnum", 999);
1047+
EXPECT_THAT(result,
1048+
HasStatus(absl::StatusCode::kNotFound,
1049+
HasSubstr("Enum value '999' not found in enum type 'test.TestEnum'")));
1050+
}
1051+
1052+
{
1053+
// Case 3: Invalid Type Name (Type does not exist)
1054+
auto result = filter_config->getEnumName("test.NonExistentEnum", 1);
1055+
EXPECT_THAT(
1056+
result,
1057+
HasStatus(absl::StatusCode::kNotFound,
1058+
HasSubstr("Enum type 'test.NonExistentEnum' not found in descriptor pool")));
1059+
}
1060+
}
1061+
9921062
TEST_F(ProtoApiScrubberFilterConfigTest, GetTypeFinder) {
9931063
absl::StatusOr<std::shared_ptr<const ProtoApiScrubberFilterConfig>> config_or_status =
9941064
ProtoApiScrubberFilterConfig::create(proto_config_, factory_context_);

0 commit comments

Comments
 (0)