1use std::{hash::Hash, pin::Pin};
2
3use serde::Deserialize;
4use topcoat_core::{context::Cx, error::Result};
5use topcoat_router::{
6 Body, Method, Methods, Path, PathBuf, Route, RouteFuture, RouteId, RouterBuilder,
7 response::IntoResponse,
8};
9use topcoat_view::ViewHandle;
10
11use crate::SignalValues;
12
13pub(crate) const SHARD_ROUTE_PREFIX: &str = "/_topcoat/runtime/shards";
14
15#[derive(Debug, Deserialize)]
21pub struct ShardRequest<A> {
22 args: A,
23 #[serde(default)]
24 signals: SignalValues,
25}
26
27impl<A> ShardRequest<A> {
28 pub fn into_parts(self) -> (A, SignalValues) {
31 (self.args, self.signals)
32 }
33}
34
35#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
37pub struct ShardId(&'static str);
38
39impl ShardId {
40 #[must_use]
41 pub const fn new(inner: &'static str) -> Self {
42 Self(inner)
43 }
44
45 #[must_use]
46 pub fn as_str(&self) -> &str {
47 self.0
48 }
49}
50
51pub type ShardFuture<'cx> = Pin<Box<dyn Future<Output = Result<ViewHandle>> + Send + 'cx>>;
54
55pub trait Shard: Send + Sync + 'static {
62 fn id(&self) -> ShardId;
64
65 fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ShardFuture<'cx>;
68}
69
70impl<S: Shard + ?Sized> Shard for &'static S {
71 fn id(&self) -> ShardId {
72 (**self).id()
73 }
74
75 fn render<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> ShardFuture<'cx> {
76 (**self).render(cx, body)
77 }
78}
79
80#[cfg(feature = "discover")]
81inventory::collect!(&'static dyn Shard);
82
83pub struct ShardRoute {
85 id: RouteId,
86 path: PathBuf,
87 shard: Box<dyn Shard>,
88}
89
90impl ShardRoute {
91 pub fn new(shard: impl Shard) -> Self {
93 Self {
94 id: RouteId::new(),
95 path: Path::new(&format!("{SHARD_ROUTE_PREFIX}/{}", shard.id().as_str())).to_owned(),
96 shard: Box::new(shard),
97 }
98 }
99}
100
101impl Route for ShardRoute {
102 fn id(&self) -> RouteId {
103 self.id
104 }
105
106 fn methods(&self) -> Methods<'_> {
107 Methods::Only(&[Method::POST])
109 }
110
111 fn path(&self) -> &Path {
112 &self.path
113 }
114
115 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
116 Box::pin(async move {
117 let view = self.shard.render(cx, body).await?;
118 view.into_response(cx)
119 })
120 }
121}
122
123pub trait RouterBuilderShardExt {
125 #[must_use]
127 fn shard(self, shard: impl Shard) -> Self;
128
129 #[cfg(feature = "discover")]
131 #[must_use]
132 fn discover_shards(self) -> Self;
133}
134
135impl RouterBuilderShardExt for RouterBuilder {
136 fn shard(self, shard: impl Shard) -> Self {
137 self.route(ShardRoute::new(shard))
138 }
139
140 #[cfg(feature = "discover")]
141 fn discover_shards(mut self) -> Self {
142 for &shard in inventory::iter::<&'static dyn Shard>() {
143 self = self.shard(shard);
144 }
145 self
146 }
147}