cratestack_client_rust/client/route.rs
1//! The route a call binds into its COSE envelope (cratestack#1007).
2//!
3//! A sealed request binds the route *template* (`/widgets/{id}`), not the
4//! URL it was sent to, plus the values the template's parameters took: a
5//! gateway may rewrite the path, and the template alone would not tell the
6//! answer for `/widgets/1` from the answer for `/widgets/2`. The client
7//! builds concrete paths, so the generated code says which template a call
8//! is for. For RPC the route is the op id.
9
10/// The route template of one call, and the values of its `{parameters}` in
11/// template order.
12///
13/// The template is the schema's own path string, the same one the server
14/// lists in `ROUTE_TRANSPORTS`, without the mount prefix; for an RPC call it
15/// is the op id (`model.Widget.get`, `procedure.ping`) or `batch`. Without
16/// the `cose` feature, or without an envelope on the client, a `RouteRef`
17/// is ignored.
18///
19/// ```
20/// use cratestack_client_rust::RouteRef;
21///
22/// let id = 7.to_string();
23/// let params = [id.as_str()];
24/// let route = RouteRef::new("/widgets/{id}", ¶ms);
25/// assert_eq!(route.template(), "/widgets/{id}");
26/// assert_eq!(route.params(), ["7"]);
27/// assert_eq!(RouteRef::rpc("model.Widget.list").params().len(), 0);
28/// ```
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct RouteRef<'a> {
31 template: &'a str,
32 params: &'a [&'a str],
33}
34
35impl<'a> RouteRef<'a> {
36 /// A REST route: its template and the decoded values of its parameters,
37 /// in template order.
38 pub const fn new(template: &'a str, params: &'a [&'a str]) -> Self {
39 Self { template, params }
40 }
41
42 /// An RPC route: the op id, or `"batch"`. RPC has no path parameters.
43 pub const fn rpc(op_id: &'a str) -> Self {
44 Self {
45 template: op_id,
46 params: &[],
47 }
48 }
49
50 pub const fn template(&self) -> &'a str {
51 self.template
52 }
53
54 pub const fn params(&self) -> &'a [&'a str] {
55 self.params
56 }
57}
58
59/// `segment` as one URL path segment: everything but the RFC 3986 unreserved
60/// characters is percent-encoded, so an id such as `50%off` or `a/b` reaches
61/// the server as the value the seal binds.
62///
63/// ```
64/// use cratestack_client_rust::encode_path_segment;
65///
66/// assert_eq!(encode_path_segment("abc-1.2_3~"), "abc-1.2_3~");
67/// assert_eq!(encode_path_segment("50%off"), "50%25off");
68/// assert_eq!(encode_path_segment("a/b c"), "a%2Fb%20c");
69/// ```
70pub fn encode_path_segment(segment: &str) -> String {
71 use std::fmt::Write as _;
72 let mut out = String::with_capacity(segment.len());
73 for byte in segment.bytes() {
74 if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') {
75 out.push(char::from(byte));
76 } else {
77 let _ = write!(out, "%{byte:02X}");
78 }
79 }
80 out
81}