xds_api/
any.rs

1use crate::generated::envoy::{
2    config::{
3        cluster::v3 as xds_cluster, endpoint::v3 as xds_endpoint, listener::v3 as xds_listener,
4        route::v3 as xds_route,
5    },
6    extensions::filters::network::http_connection_manager::v3 as xds_http,
7    extensions::transport_sockets::tls::v3 as xds_tls,
8    service::runtime::v3 as xds_runtime,
9};
10use crate::generated::google::protobuf;
11
12macro_rules! well_known_types {
13    ($(#[$id_attr:meta])* pub enum $id_name:ident {  $($(#[$variant_attr:meta])* $variant:ident => $xds_type:ty),* $(,)* }) => {
14        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
15        #[derive(enum_map::Enum)]
16        $(#[$id_attr])*
17        pub enum $id_name {
18            $(
19                $(#[$variant_attr])*
20                $variant,
21            )*
22        }
23
24        impl $id_name {
25            pub fn all() -> &'static [$id_name] {
26                &[
27                    $(
28                        $id_name::$variant,
29                    )*
30                ]
31            }
32
33            pub fn from_type_url(type_url: &str) -> Option<Self> {
34                use prost::Name;
35
36                static FROM_TYPE_URL: std::sync::LazyLock<Box<[(String, $id_name)]>> = std::sync::LazyLock::new(|| {
37                    let urls = vec![
38                        $(
39                            (<$xds_type>::type_url(), $id_name::$variant),
40                        )*
41                    ];
42                    urls.into_boxed_slice()
43                });
44
45                FROM_TYPE_URL.iter().find(|(k, _)| k == type_url).map(|(_, v)| *v)
46            }
47
48            pub fn type_url(&self) -> &'static str {
49                use prost::Name;
50
51                static TO_TYPE_URL: std::sync::LazyLock<enum_map::EnumMap<$id_name, String>> = std::sync::LazyLock::new(|| {
52                    enum_map::enum_map! {
53                        $(
54                            $id_name::$variant => <$xds_type>::type_url(),
55                        )*
56                    }
57                });
58
59                TO_TYPE_URL[*self].as_str()
60            }
61
62            #[cfg(feature = "pbjson")]
63            fn decode(&self, bs: &[u8]) -> Result<JsonAny, prost::DecodeError> {
64                match self {
65                    $(
66                        $id_name::$variant => Ok(JsonAny::$variant(prost::Message::decode(bs)?)),
67                    )*
68                }
69            }
70        }
71
72        #[cfg(feature = "pbjson")]
73        #[allow(clippy::large_enum_variant)]
74        #[derive(Debug)]
75        enum JsonAny {
76            $(
77                $variant($xds_type),
78            )*
79        }
80
81        #[cfg(feature = "pbjson")]
82        impl serde::Serialize for JsonAny {
83            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84            where
85                S: serde::Serializer,
86            {
87                #[derive(serde::Serialize)]
88                struct AnyWrapper<'a, T> {
89                    #[serde(rename = "@type")]
90                    type_url: &'static str,
91
92                    #[serde(flatten)]
93                    value: &'a T,
94                }
95
96                match self {
97                    $(
98                        JsonAny::$variant(ref inner) => {
99                            let type_url = $id_name::$variant.type_url();
100                            let wrapped = AnyWrapper {
101                                type_url,
102                                value: inner,
103                            };
104                            wrapped.serialize(serializer)
105                        }
106                    )*
107                }
108            }
109        }
110    };
111}
112
113well_known_types! {
114    /// Well known types that [Any][protobuf::Any] messages may contain while
115    /// dealing with XDS `DiscoveryRequest`s and `DiscoveryResponse`s.
116    ///
117    /// This type currently suppors the top-level types for xDS services and the
118    /// `HttpConnectionManager` type that most applications will need to work
119    /// with while managing a `Listener`. It doesn't include support for
120    /// extension types or typed configs.
121    ///
122    /// ```no_run
123    /// match WellKnownTypes::from_type_url(&any.type_url) {
124    ///     Some(WellKnownTypes::Listener) => {
125    ///         do_something_with_listener(Listener::decode(&any.value).unwrap())
126    ///     }
127    ///     _ => todo!(),
128    /// }
129    /// ```
130    ///
131    /// # `pbjson`
132    ///
133    /// With the `pbjson` feature enabled, [Any][protobuf::Any] messages use
134    /// `WellKnownTypes` for canonical json conversion and any type listed in
135    /// `WellKnownTypes` will be serialized as a struct instead of as an opaque
136    /// blob. For example, an `Any` containing an
137    /// [HttpConnectionManager][xds_http::HttpConnectionManager] would serialize
138    /// to.
139    ///
140    /// ```no_run,json
141    /// {
142    ///     "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
143    ///     "route_config": {
144    ///         "name": "test_route"
145    ///     }
146    /// }
147    /// ```
148    pub enum WellKnownTypes {
149        /// An XDS Listener for LDS.
150        Listener => xds_listener::Listener,
151
152        /// An HttpConnectionManager, included in XDS Listeners.
153        HttpConnectionManager => xds_http::HttpConnectionManager,
154
155        /// A `RouteConfiguration` type, used in RDS.
156        RouteConfiguration => xds_route::RouteConfiguration,
157
158        /// A `ScopedRouteConfiguration`, used in SRDS.
159        ScopedRouteConfiguration => xds_route::ScopedRouteConfiguration,
160
161        /// A `VirtualHost`, used in VHDS.
162        VirtualHost => xds_route::VirtualHost,
163
164        /// A `Cluster`, used in CDS
165        Cluster => xds_cluster::Cluster,
166
167        /// A `ClusterLoadAssignment`, used in EDS.
168        ClusterLoadAssignment => xds_endpoint::ClusterLoadAssignment,
169
170        /// A `Secret`, used in SDS.
171        Secret => xds_tls::Secret,
172
173        /// A `Runtime`, used in RTDS.
174        Runtime => xds_runtime::Runtime,
175    }
176}
177
178impl protobuf::Any {
179    pub fn from_msg<M: prost::Name>(m: &M) -> Result<Self, prost::EncodeError> {
180        let type_url = M::type_url();
181        let mut value = Vec::new();
182        prost::Message::encode(m, &mut value)?;
183
184        Ok(Self { type_url, value })
185    }
186
187    pub fn to_msg<M: prost::Name + Default + Sized>(&self) -> Result<M, prost::DecodeError> {
188        let expected_url = M::type_url();
189
190        if self.type_url != expected_url {
191            return Err(prost::DecodeError::new(format!(
192                "unexpected type URL: \"{}\": message url: \"{}\"",
193                &self.type_url, &expected_url,
194            )));
195        }
196
197        M::decode(self.value.as_slice())
198    }
199}
200
201#[cfg(feature = "pbjson")]
202impl serde::Serialize for protobuf::Any {
203    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
204    where
205        S: serde::Serializer,
206    {
207        use serde::ser::SerializeStruct;
208
209        match WellKnownTypes::from_type_url(&self.type_url) {
210            Some(wkt) => {
211                let type_url = wkt.type_url();
212
213                let wk_struct = wkt.decode(&self.value).map_err(|_| {
214                    serde::ser::Error::custom(format!(
215                        "failed to transcode google.protobuf.Any into {type_url}",
216                    ))
217                })?;
218
219                wk_struct.serialize(serializer)
220            }
221            None => {
222                let mut struct_ser = serializer.serialize_struct("google.protobuf.Any", 2)?;
223                struct_ser.serialize_field("@type", &self.type_url)?;
224                struct_ser.serialize_field("value", &self.value)?;
225                struct_ser.end()
226            }
227        }
228    }
229}
230
231#[cfg(feature = "pbjson")]
232impl<'de> serde::Deserialize<'de> for protobuf::Any {
233    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
234    where
235        D: serde::Deserializer<'de>,
236    {
237        const FIELDS: &[&str] = &["@type", "type_url", "typeUrl", "value"];
238
239        enum Field {
240            TypeUrl,
241            Value,
242        }
243
244        impl<'de> serde::Deserialize<'de> for Field {
245            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246            where
247                D: serde::Deserializer<'de>,
248            {
249                struct Visitor;
250
251                impl<'de> serde::de::Visitor<'de> for Visitor {
252                    type Value = Field;
253
254                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
255                        write!(formatter, "expected one of: {FIELDS:?}")
256                    }
257
258                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
259                    where
260                        E: serde::de::Error,
261                    {
262                        match v {
263                            "@type" | "type_url" | "typeUrl" => Ok(Field::TypeUrl),
264                            "value" => Ok(Field::Value),
265                            _ => Err(serde::de::Error::unknown_field(v, FIELDS)),
266                        }
267                    }
268                }
269
270                deserializer.deserialize_identifier(Visitor)
271            }
272        }
273
274        struct Visitor;
275
276        impl<'de> serde::de::Visitor<'de> for Visitor {
277            type Value = protobuf::Any;
278
279            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
280                formatter.write_str("google.protobuf.Any")
281            }
282
283            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
284            where
285                A: serde::de::MapAccess<'de>,
286            {
287                let mut type_url = None;
288                let mut value = None;
289
290                while let Some(k) = map.next_key()? {
291                    match k {
292                        Field::TypeUrl => {
293                            if type_url.is_some() {
294                                return Err(serde::de::Error::duplicate_field("type_url"));
295                            }
296                            type_url = Some(map.next_value()?);
297                        }
298                        Field::Value => {
299                            if value.is_some() {
300                                return Err(serde::de::Error::duplicate_field("value"));
301                            }
302                            value = Some(map.next_value()?);
303                        }
304                    }
305                }
306
307                Ok(protobuf::Any {
308                    type_url: type_url.unwrap_or_default(),
309                    value: value.unwrap_or_default(),
310                })
311            }
312        }
313
314        deserializer.deserialize_struct("google.protobuf.Any", FIELDS, Visitor)
315    }
316}