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}