Skip to content

Instantly share code, notes, and snippets.

@mhutter
Created December 14, 2023 20:44
Show Gist options
  • Save mhutter/3d549530146fee7d614cb2eee99ec1b6 to your computer and use it in GitHub Desktop.
Save mhutter/3d549530146fee7d614cb2eee99ec1b6 to your computer and use it in GitHub Desktop.
Shuttle.rs in less than 100 lines of Rust
use std::{future::Future, pin::Pin};
use async_trait::async_trait;
use tokio::net::TcpListener;
type PinFuture<O> = Pin<Box<dyn Future<Output = O> + Send>>;
/// Run your application
pub fn run<M, S, T>(app: M)
where
M: MakeServer<T, S>,
S: Server,
{
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(async {
let listener = TcpListener::bind("127.0.0.1:3000")
.await
.expect("bind port");
println!("Listening to http://{}", listener.local_addr().unwrap());
app.into_server().await.start(listener).await
});
}
/// Trait for async functions that can "make" a new server
///
/// Implementations are provided for function items with zero up to TBD arguments that each must
/// implement [`Inject`].
pub trait MakeServer<T, S: Server>: Send + 'static {
/// The future calling this `MakeServer` returns
type Future: Future<Output = S> + Send + 'static;
/// Call the `MakeServer`, injecting all dependencies
fn into_server(self) -> Self::Future;
}
impl<F, S> MakeServer<((),), S> for F
where
F: FnOnce() -> S + Send + 'static,
S: Server + 'static,
{
type Future = PinFuture<S>;
fn into_server(self) -> Self::Future {
Box::pin(async move { self() })
}
}
#[async_trait]
impl<F, S, T0> MakeServer<(T0,), S> for F
where
F: FnOnce(T0) -> S + Send + 'static,
S: Server + 'static,
T0: Inject,
{
type Future = PinFuture<S>;
fn into_server(self) -> Self::Future {
Box::pin(async move { self(Inject::inject().await) })
}
}
/// Types that can be "conjured up" magically in the background, and then injected into
/// [`MakeServer`] functions.
#[async_trait]
pub trait Inject {
/// Create, by whatever means necessary, the desired type.
async fn inject() -> Self;
}
#[async_trait]
impl Inject for i32 {
async fn inject() -> Self {
42
}
}
/// The Server trait defines how to start an application
#[async_trait]
pub trait Server {
/// Start handling requests on the given TCP listener.
async fn start(self, listener: TcpListener);
}
#[cfg(feature = "axum")]
#[async_trait]
impl Server for axum::Router {
async fn start(self, listener: TcpListener) {
axum::serve(listener, self.into_make_service())
.await
.expect("start server");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment