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