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)
}
}