1use std::{hash::Hash, marker::PhantomData, pin::Pin};
2
3use ref_cast::RefCast;
4use serde::{Deserialize, Serialize};
5use topcoat_core::{context::Cx, error::Result};
6use topcoat_router::{
7 Body, Method, Methods, Path, PathBuf, Response, Route, RouteFuture, RouterBuilder,
8};
9
10use crate::Surrogated;
11
12const PROCEDURE_ROUTE_PREFIX: &str = "/_topcoat/procedures";
13
14#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ProcedureId(&'static str);
17
18impl ProcedureId {
19 #[must_use]
20 pub const fn new(inner: &'static str) -> Self {
21 Self(inner)
22 }
23
24 #[must_use]
25 fn as_str(&self) -> &str {
26 self.0
27 }
28}
29
30pub type ProcedureHandlerFn =
31 for<'cx> fn(
32 cx: &'cx Cx,
33 body: Body,
34 ) -> Pin<Box<dyn Future<Output = Result<Response>> + Send + 'cx>>;
35
36#[derive(Debug, Clone)]
37pub struct Procedure<A, R> {
38 id: ProcedureId,
39 handle: ProcedureHandlerFn,
40 _phantom: PhantomData<fn(A) -> R>,
41}
42
43impl<A, R> Procedure<A, R> {
44 #[inline]
45 pub const fn new(id: ProcedureId, handle: ProcedureHandlerFn) -> Self {
46 Self {
47 id,
48 handle,
49 _phantom: PhantomData,
50 }
51 }
52
53 #[must_use]
54 pub fn id(&self) -> ProcedureId {
55 self.id
56 }
57}
58
59#[derive(Debug, Clone)]
60pub struct ErasedProcedure {
61 id: ProcedureId,
62 handle: ProcedureHandlerFn,
63}
64
65impl ErasedProcedure {
66 #[must_use]
67 pub const fn new<A, R>(procedure: &Procedure<A, R>) -> Self {
68 Self {
69 id: procedure.id,
70 handle: procedure.handle,
71 }
72 }
73
74 #[must_use]
75 pub fn id(&self) -> ProcedureId {
76 self.id
77 }
78
79 #[inline]
85 pub async fn handle(&self, cx: &Cx, body: Body) -> Result<Response> {
86 (self.handle)(cx, body).await
87 }
88}
89
90impl<A, R> From<Procedure<A, R>> for ErasedProcedure {
91 fn from(value: Procedure<A, R>) -> Self {
92 Self {
93 id: value.id,
94 handle: value.handle,
95 }
96 }
97}
98
99impl<A, R> From<&Procedure<A, R>> for ErasedProcedure {
100 fn from(value: &Procedure<A, R>) -> Self {
101 Self::new(value)
102 }
103}
104
105#[cfg(feature = "discover")]
106inventory::collect!(ErasedProcedure);
107
108#[derive(Debug, Clone)]
110pub struct ProcedureRoute {
111 path: PathBuf,
112 procedure: ErasedProcedure,
113}
114
115impl ProcedureRoute {
116 pub fn new(procedure: impl Into<ErasedProcedure>) -> Self {
118 let procedure = procedure.into();
119 Self {
120 path: Path::new(&format!(
121 "{PROCEDURE_ROUTE_PREFIX}/{}",
122 procedure.id().as_str()
123 ))
124 .to_owned(),
125 procedure,
126 }
127 }
128}
129
130impl Route for ProcedureRoute {
131 fn methods(&self) -> Methods<'_> {
132 Methods::Only(&[Method::POST])
133 }
134
135 fn path(&self) -> &Path {
136 &self.path
137 }
138
139 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
140 Box::pin(async move { self.procedure.handle(cx, body).await })
141 }
142}
143
144pub trait RouterBuilderProcedureExt {
146 #[must_use]
148 fn procedure(self, procedure: impl Into<ErasedProcedure>) -> Self;
149
150 #[cfg(feature = "discover")]
152 #[must_use]
153 fn discover_procedures(self) -> Self;
154}
155
156impl RouterBuilderProcedureExt for RouterBuilder {
157 fn procedure(self, procedure: impl Into<ErasedProcedure>) -> Self {
158 self.route(ProcedureRoute::new(procedure))
159 }
160
161 #[cfg(feature = "discover")]
162 fn discover_procedures(mut self) -> Self {
163 for procedure in inventory::iter::<ErasedProcedure>().cloned() {
164 self = self.procedure(procedure);
165 }
166 self
167 }
168}
169
170#[derive(Debug, RefCast)]
171#[repr(transparent)]
172pub struct ProcedureSurrogate<A, R>(Procedure<A, R>);
173
174impl<A, R> ProcedureSurrogate<A, R> {
175 pub(crate) const fn new(v: Procedure<A, R>) -> Self {
176 Self(v)
177 }
178}
179
180impl<A, R> ProcedureSurrogate<A, R>
181where
182 A: Surrogated,
183 R: Surrogated,
184{
185 #[allow(clippy::unused_async)]
191 pub async fn call(&self, _args: A::Surrogate) -> R::Surrogate {
192 panic!("procedures cannot be executed on the server");
193 }
194}
195
196crate::impl_surrogate!({A, R} Procedure<A, R>, ProcedureSurrogate<A, R>);
197crate::impl_surrogate_ref!({A, R} Procedure<A, R>, ProcedureSurrogate<A, R>);
198crate::impl_surrogate_mut!({A, R} Procedure<A, R>, ProcedureSurrogate<A, R>);
199
200impl<A, R> Serialize for ProcedureSurrogate<A, R> {
201 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
202 where
203 S: serde::Serializer,
204 {
205 #[derive(Serialize)]
206 struct TaggedProcedure {
207 t: &'static str,
208 id: ProcedureId,
209 }
210
211 TaggedProcedure {
212 t: "Procedure",
213 id: self.0.id(),
214 }
215 .serialize(serializer)
216 }
217}