Created
December 14, 2023 20:44
-
-
Save mhutter/3d549530146fee7d614cb2eee99ec1b6 to your computer and use it in GitHub Desktop.
Shuttle.rs in less than 100 lines of Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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