Adds enum field trial parser.

Removed the need to create a custom parser function and reuses some of
the code to reduce binary overhead of enums.

Bug: webrtc:9346
Change-Id: I51c9da713ed5456a86a2afbcf0991477bb83b894
Reviewed-on: https://webrtc-review.googlesource.com/83623
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23752}
diff --git a/rtc_base/experiments/field_trial_parser.cc b/rtc_base/experiments/field_trial_parser.cc
index 4ea603f..55299ad 100644
--- a/rtc_base/experiments/field_trial_parser.cc
+++ b/rtc_base/experiments/field_trial_parser.cc
@@ -121,6 +121,37 @@
   return true;
 }
 
+AbstractFieldTrialEnum::AbstractFieldTrialEnum(
+    std::string key,
+    int default_value,
+    std::map<std::string, int> mapping)
+    : FieldTrialParameterInterface(key),
+      value_(default_value),
+      enum_mapping_(mapping) {
+  for (auto& key_val : enum_mapping_)
+    valid_values_.insert(key_val.second);
+}
+AbstractFieldTrialEnum::AbstractFieldTrialEnum(const AbstractFieldTrialEnum&) =
+    default;
+AbstractFieldTrialEnum::~AbstractFieldTrialEnum() = default;
+
+bool AbstractFieldTrialEnum::Parse(absl::optional<std::string> str_value) {
+  if (str_value) {
+    auto it = enum_mapping_.find(*str_value);
+    if (it != enum_mapping_.end()) {
+      value_ = it->second;
+      return true;
+    }
+    absl::optional<int> value = ParseTypedParameter<int>(*str_value);
+    if (value.has_value() &&
+        (valid_values_.find(*value) != valid_values_.end())) {
+      value_ = *value;
+      return true;
+    }
+  }
+  return false;
+}
+
 template class FieldTrialParameter<bool>;
 template class FieldTrialParameter<double>;
 template class FieldTrialParameter<int>;
diff --git a/rtc_base/experiments/field_trial_parser.h b/rtc_base/experiments/field_trial_parser.h
index ff00b90..a385ccf 100644
--- a/rtc_base/experiments/field_trial_parser.h
+++ b/rtc_base/experiments/field_trial_parser.h
@@ -12,6 +12,8 @@
 
 #include <stdint.h>
 #include <initializer_list>
+#include <map>
+#include <set>
 #include <string>
 #include "absl/types/optional.h"
 
@@ -83,6 +85,47 @@
   T value_;
 };
 
+class AbstractFieldTrialEnum : public FieldTrialParameterInterface {
+ public:
+  AbstractFieldTrialEnum(std::string key,
+                         int default_value,
+                         std::map<std::string, int> mapping);
+  ~AbstractFieldTrialEnum() override;
+  AbstractFieldTrialEnum(const AbstractFieldTrialEnum&);
+
+ protected:
+  bool Parse(absl::optional<std::string> str_value) override;
+
+ protected:
+  int value_;
+  std::map<std::string, int> enum_mapping_;
+  std::set<int> valid_values_;
+};
+
+// The FieldTrialEnum class can be used to quickly define a parser for a
+// specific enum. It handles values provided as integers and as strings if a
+// mapping is provided.
+template <typename T>
+class FieldTrialEnum : public AbstractFieldTrialEnum {
+ public:
+  FieldTrialEnum(std::string key,
+                 T default_value,
+                 std::map<std::string, T> mapping)
+      : AbstractFieldTrialEnum(key,
+                               static_cast<int>(default_value),
+                               ToIntMap(mapping)) {}
+  T Get() const { return static_cast<T>(value_); }
+  operator T() const { return Get(); }
+
+ private:
+  static std::map<std::string, int> ToIntMap(std::map<std::string, T> mapping) {
+    std::map<std::string, int> res;
+    for (const auto& it : mapping)
+      res[it.first] = static_cast<int>(it.second);
+    return res;
+  }
+};
+
 // This class uses the ParseTypedParameter function to implement an optional
 // parameter implementation that can default to absl::nullopt.
 template <typename T>
diff --git a/rtc_base/experiments/field_trial_parser_unittest.cc b/rtc_base/experiments/field_trial_parser_unittest.cc
index 73bddfd..69f35bd 100644
--- a/rtc_base/experiments/field_trial_parser_unittest.cc
+++ b/rtc_base/experiments/field_trial_parser_unittest.cc
@@ -34,25 +34,12 @@
 };
 
 enum class CustomEnum {
-  kDefault,
-  kRed,
-  kBlue,
+  kDefault = 0,
+  kRed = 1,
+  kBlue = 2,
 };
 }  // namespace
 
-// Providing a custom parser for an enum can make the trial string easier to
-// read, but also adds more code and makes the string more verbose.
-template <>
-absl::optional<CustomEnum> ParseTypedParameter<CustomEnum>(std::string str) {
-  if (str == "default")
-    return CustomEnum::kDefault;
-  else if (str == "red")
-    return CustomEnum::kRed;
-  else if (str == "blue")
-    return CustomEnum::kBlue;
-  return absl::nullopt;
-}
-
 TEST(FieldTrialParserTest, ParsesValidParameters) {
   DummyExperiment exp("Enabled,f:-1.7,r:2,p:1,h:x7c");
   EXPECT_TRUE(exp.enabled.Get());
@@ -122,10 +109,17 @@
   EXPECT_FALSE(optional_string.Get().has_value());
 }
 TEST(FieldTrialParserTest, ParsesCustomEnumParameter) {
-  FieldTrialParameter<CustomEnum> my_enum("e", CustomEnum::kDefault);
+  FieldTrialEnum<CustomEnum> my_enum("e", CustomEnum::kDefault,
+                                     {{"default", CustomEnum::kDefault},
+                                      {"red", CustomEnum::kRed},
+                                      {"blue", CustomEnum::kBlue}});
   ParseFieldTrial({&my_enum}, "");
   EXPECT_EQ(my_enum.Get(), CustomEnum::kDefault);
   ParseFieldTrial({&my_enum}, "e:red");
   EXPECT_EQ(my_enum.Get(), CustomEnum::kRed);
+  ParseFieldTrial({&my_enum}, "e:2");
+  EXPECT_EQ(my_enum.Get(), CustomEnum::kBlue);
+  ParseFieldTrial({&my_enum}, "e:5");
+  EXPECT_EQ(my_enum.Get(), CustomEnum::kBlue);
 }
 }  // namespace webrtc