Don't allocate a `String` or validate UTF-8 for parsing a crubit feature name.

(We still reject invalid utf-8, just because it doesn't match the list of strings.)

As far as I understand it, when passed `&[u8]` or `&str`, serde_json will try its hardest to avoid allocating a new string. In particular, if there's no escapes, then it can reuse the underlying string storage.

`&[u8]`, in addition, allows it to skip UTF-8 validation as an implementation extension. See https://docs.rs/serde_json/latest/serde_json/de/struct.Deserializer.html#method.deserialize_bytes.

PiperOrigin-RevId: 665582405
Change-Id: I8f4b409841260b84e98e1a00a21b0651d0be5088
diff --git a/common/crubit_feature.rs b/common/crubit_feature.rs
index 0a81d12..c9df354 100644
--- a/common/crubit_feature.rs
+++ b/common/crubit_feature.rs
@@ -43,17 +43,40 @@
     where
         D: serde::Deserializer<'de>,
     {
-        let features = match <String as Deserialize<'de>>::deserialize(deserializer)?.as_str() {
-            "all" => flagset::FlagSet::<CrubitFeature>::full(),
-            "supported" => CrubitFeature::Supported.into(),
-            "experimental" => CrubitFeature::Experimental.into(),
-            other => {
-                return Err(<D::Error as serde::de::Error>::custom(format!(
-                    "Unexpected Crubit feature: {other}"
-                )));
+        // we can't just deserialize as a `&[u8]` or similar, because the bytes may be
+        // ephemeral (e.g. from a `\u` escape). Aside from that, serde_json also
+        // insists on handing out allocated strings sometimes. So we write our
+        // own visitor, which can always handle even ephemeral bytestrings.
+        struct CrubitFeatureVisitor;
+
+        impl<'de> serde::de::Visitor<'de> for CrubitFeatureVisitor {
+            type Value = SerializedCrubitFeature;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("a Crubit feature flag name")
             }
-        };
-        Ok(SerializedCrubitFeature(features))
+
+            fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                let features = match bytes {
+                    b"all" => flagset::FlagSet::<CrubitFeature>::full(),
+                    b"supported" => CrubitFeature::Supported.into(),
+                    b"experimental" => CrubitFeature::Experimental.into(),
+                    other => {
+                        return Err(E::custom(format!(
+                            "Unexpected Crubit feature: {:?}",
+                            String::from_utf8_lossy(other)
+                        )));
+                    }
+                };
+
+                Ok(SerializedCrubitFeature(features))
+            }
+        }
+
+        deserializer.deserialize_bytes(CrubitFeatureVisitor)
     }
 }