Skip to main content

structured_proxy/transcode/
select.rs

1//! Which annotated RPCs become REST routes.
2
3use std::sync::Arc;
4
5use prost_reflect::{DescriptorPool, MethodDescriptor};
6
7/// The RPCs transcoded to REST: every one with a `google.api.http` rule, or
8/// only the services and methods named.
9///
10/// # Examples
11///
12/// ```
13/// use structured_proxy::transcode::RpcSelection;
14///
15/// let pool = prost_reflect::DescriptorPool::new();
16/// // An empty list selects every annotated RPC.
17/// let all = RpcSelection::new(&pool, Vec::<String>::new()).unwrap();
18/// # let _ = all;
19/// // A name the descriptors do not hold is an error.
20/// assert!(RpcSelection::new(&pool, ["acme.v1.Orders"]).is_err());
21/// ```
22#[derive(Clone, Debug, Default)]
23pub struct RpcSelection {
24    names: Arc<[Name]>,
25}
26
27/// A selected service, or one method of it.
28#[derive(Debug)]
29struct Name {
30    service: String,
31    method: Option<String>,
32}
33
34impl RpcSelection {
35    /// `names` out of `pool`: `package.Service` for every RPC of a service,
36    /// `package.Service/Method` for one; none selects every annotated RPC.
37    ///
38    /// # Errors
39    ///
40    /// A name that is not a service, or a method of a service, of `pool`: a
41    /// typo would otherwise leave its routes silently unmounted.
42    pub fn new(
43        pool: &DescriptorPool,
44        names: impl IntoIterator<Item = impl AsRef<str>>,
45    ) -> Result<Self, String> {
46        let names = names
47            .into_iter()
48            .map(|name| {
49                let name = name.as_ref();
50                let (service, method) = match name.split_once('/') {
51                    Some((service, method)) => (service, Some(method)),
52                    None => (name, None),
53                };
54                let known = pool.get_service_by_name(service).is_some_and(|found| {
55                    method.is_none_or(|method| found.methods().any(|m| m.name() == method))
56                });
57                if !known {
58                    return Err(format!(
59                        "{name:?} is not a service or method of the proto descriptors"
60                    ));
61                }
62                Ok(Name {
63                    service: service.to_owned(),
64                    method: method.map(str::to_owned),
65                })
66            })
67            .collect::<Result<Vec<_>, _>>()?;
68        Ok(Self {
69            names: names.into(),
70        })
71    }
72
73    /// Whether every annotated RPC is transcoded.
74    pub(crate) fn is_all(&self) -> bool {
75        self.names.is_empty()
76    }
77
78    /// Whether `method` is transcoded.
79    pub(crate) fn selects(&self, method: &MethodDescriptor) -> bool {
80        self.is_all() || {
81            let service = method.parent_service();
82            self.names.iter().any(|name| {
83                name.service == service.full_name()
84                    && name.method.as_deref().is_none_or(|m| m == method.name())
85            })
86        }
87    }
88}