1use std::{hash::Hash, pin::Pin};
2
3use topcoat_core::{
4 context::Cx,
5 error::{Error, Result},
6};
7
8use topcoat_router::{
9 Body, IntoResponse, Method, Path, PathBuf, Route, RouteFuture, RouterBuilder,
10};
11use topcoat_view::View;
12
13pub(crate) const SHARD_ROUTE_PREFIX: &str = "/_topcoat/shards";
14
15#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
16pub struct ShardId(&'static str);
17
18impl ShardId {
19 #[must_use]
20 pub const fn new(inner: &'static str) -> Self {
21 Self(inner)
22 }
23
24 #[must_use]
25 pub fn as_str(&self) -> &str {
26 self.0
27 }
28}
29
30pub type ShardRenderFn =
31 for<'cx> fn(
32 cx: &'cx Cx,
33 body: Body,
34 ) -> Pin<Box<dyn Future<Output = Result<View, Error>> + Send + 'cx>>;
35
36#[derive(Debug, Clone)]
37pub struct ErasedShard {
38 id: ShardId,
39 render: ShardRenderFn,
40}
41
42impl ErasedShard {
43 #[must_use]
44 pub const fn new(id: ShardId, render: ShardRenderFn) -> Self {
45 Self { id, render }
46 }
47
48 #[must_use]
49 pub fn id(&self) -> ShardId {
50 self.id
51 }
52
53 #[inline]
61 pub async fn render(&self, cx: &Cx, body: Body) -> Result<View> {
62 (self.render)(cx, body).await
63 }
64}
65
66#[cfg(feature = "discover")]
67inventory::collect!(ErasedShard);
68
69pub struct ShardRoute {
70 path: PathBuf,
71 shard: ErasedShard,
72}
73
74impl ShardRoute {
75 pub fn new(shard: impl Into<ErasedShard>) -> Self {
77 let shard = shard.into();
78 Self {
79 path: Path::new(&format!("{SHARD_ROUTE_PREFIX}/{}", shard.id().as_str())).to_owned(),
80 shard,
81 }
82 }
83}
84
85impl Route for ShardRoute {
86 fn method(&self) -> Method {
87 Method::POST
89 }
90
91 fn path(&self) -> &Path {
92 &self.path
93 }
94
95 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
96 Box::pin(async move {
97 let view = (self.shard.render)(cx, body).await?;
98 view.into_response(cx)
99 })
100 }
101}
102
103pub trait RouterBuilderShardExt {
105 #[must_use]
107 fn shard(self, shard: impl Into<ErasedShard>) -> Self;
108
109 #[cfg(feature = "discover")]
111 #[must_use]
112 fn discover_shards(self) -> Self;
113}
114
115impl RouterBuilderShardExt for RouterBuilder {
116 fn shard(self, shard: impl Into<ErasedShard>) -> Self {
117 self.route(ShardRoute::new(shard))
118 }
119
120 #[cfg(feature = "discover")]
121 fn discover_shards(mut self) -> Self {
122 for shard in inventory::iter::<ErasedShard>().cloned() {
123 self = self.shard(shard);
124 }
125 self
126 }
127}