1use std::{hash::Hash, pin::Pin};
2
3use topcoat_core::{context::Cx, error::Result};
4use topcoat_router::{
5 Body, Method, Methods, Path, PathBuf, Route, RouteFuture, RouteId, RouterBuilder,
6 response::IntoResponse,
7};
8use topcoat_view::ViewHandle;
9
10pub(crate) const SHARD_ROUTE_PREFIX: &str = "/_topcoat/shards";
11
12#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
14pub struct ShardId(&'static str);
15
16impl ShardId {
17 #[must_use]
18 pub const fn new(inner: &'static str) -> Self {
19 Self(inner)
20 }
21
22 #[must_use]
23 pub fn as_str(&self) -> &str {
24 self.0
25 }
26}
27
28pub type ShardFuture<'cx> = Pin<Box<dyn Future<Output = Result<ViewHandle>> + Send + 'cx>>;
31
32pub trait Shard: Send + Sync + 'static {
39 fn id(&self) -> ShardId;
41
42 fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ShardFuture<'cx>;
45}
46
47impl<S: Shard + ?Sized> Shard for &'static S {
48 fn id(&self) -> ShardId {
49 (**self).id()
50 }
51
52 fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ShardFuture<'cx> {
53 (**self).render(cx, body)
54 }
55}
56
57#[cfg(feature = "discover")]
58inventory::collect!(&'static dyn Shard);
59
60pub struct ShardRoute {
62 id: RouteId,
63 path: PathBuf,
64 shard: Box<dyn Shard>,
65}
66
67impl ShardRoute {
68 pub fn new(shard: impl Shard) -> Self {
70 Self {
71 id: RouteId::new(),
72 path: Path::new(&format!("{SHARD_ROUTE_PREFIX}/{}", shard.id().as_str())).to_owned(),
73 shard: Box::new(shard),
74 }
75 }
76}
77
78impl Route for ShardRoute {
79 fn id(&self) -> RouteId {
80 self.id
81 }
82
83 fn methods(&self) -> Methods<'_> {
84 Methods::Only(&[Method::POST])
86 }
87
88 fn path(&self) -> &Path {
89 &self.path
90 }
91
92 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
93 Box::pin(async move {
94 let view = self.shard.render(cx, body).await?;
95 view.into_response(cx)
96 })
97 }
98}
99
100pub trait RouterBuilderShardExt {
102 #[must_use]
104 fn shard(self, shard: impl Shard) -> Self;
105
106 #[cfg(feature = "discover")]
108 #[must_use]
109 fn discover_shards(self) -> Self;
110}
111
112impl RouterBuilderShardExt for RouterBuilder {
113 fn shard(self, shard: impl Shard) -> Self {
114 self.route(ShardRoute::new(shard))
115 }
116
117 #[cfg(feature = "discover")]
118 fn discover_shards(mut self) -> Self {
119 for &shard in inventory::iter::<&'static dyn Shard>() {
120 self = self.shard(shard);
121 }
122 self
123 }
124}