- Examples
- Custom Service
Examples
Custom Service
This example will explain how to create a custom shuttle service.
cargo shuttle init --no-framework
This will simply initialize a new cargo crate with a dependency on shuttle-service
.
We also need to add sqlx
and shuttle-shared-db
for this example, make sure your
Cargo.toml
looks like the one below.
[package]
name = "custom-service"
version = "0.1.0"
edition = "2021"
[lib]
[dependencies]
shuttle-service = { version = "0.9.0" }
shuttle-shared-db = { version = "0.9.0", features = ["postgres"] }
sqlx = { version = "0.6", features = ["runtime-tokio-native-tls", "postgres"] }
This section will explain how to create a custom shuttle service. To do this we need
to return a struct from our shuttle_service::main
function that implements shuttle_service::Service
.
In this simple example we will implement Service
for a PgPool
, that will simply return ‘Hello world’
from the database on startup. In Rust we can’t implement foreign traits for foreign types, so we have
to create a wrapper around our PgPool
.
struct PoolService {
pool: sqlx::PgPool,
}
Then we need to implement shuttle_service::Service
for our wrapper. If you need to bind to an address,
for example if you’re implementing service for an HTTP server, you can use the addr
argument from bind
.
You can only have one HTTP service bound to the addr
, but you can start other services that don’t rely on
binding to a socket. A common usecase is starting an HTTP server like axum
, alongside a serenity
bot
that doesn’t need to bind to a socket. Then you would include both in the same wrapper struct, and start
them as normal in the bind
function.
#[shuttle_service::async_trait]
impl shuttle_service::Service for PoolService {
async fn bind(
mut self: Box<Self>,
_addr: std::net::SocketAddr,
) -> Result<(), shuttle_service::error::Error> {
self.start().await?;
Ok(())
}
}
To finish up, we return the wrapper struct from our shuttle_service::main
function:
use shuttle_service::error::CustomError;
#[shuttle_service::main]
async fn init(
#[shuttle_shared_db::Postgres] pool: sqlx::PgPool,
) -> Result<PoolService, shuttle_service::Error> {
Ok(PoolService { pool })
}
impl PoolService {
async fn start(&self) -> Result<(), CustomError> {
let (rec,): (String,) = sqlx::query_as("SELECT 'Hello world'")
.fetch_one(&self.pool)
.await
.map_err(CustomError::new)?;
assert_eq!(rec, "Hello world");
Ok(())
}
}
Try it out with the run
command:
cargo shuttle run