shuttle_service/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::path::PathBuf;
use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use shuttle_common::constants::STORAGE_DIRNAME;
pub use shuttle_common::{
database,
deployment::{DeploymentMetadata, Environment},
resource::{self, ShuttleResourceOutput},
secrets::Secret,
ContainerRequest, ContainerResponse, DatabaseInfo, DatabaseResource, DbInput, SecretStore,
};
pub use crate::error::{CustomError, Error};
#[cfg(feature = "builder")]
pub mod builder;
pub mod error;
#[cfg(feature = "runner")]
pub mod runner;
/// Allows implementing plugins for the Shuttle main function.
///
/// ## Creating your own Shuttle plugin
///
/// You can add your own implementation of this trait along with [`IntoResource`] to customize the
/// input type `R` that gets into the Shuttle main function on an existing resource.
///
/// You can also make your own plugin, for example to generalise the connection logic to a third-party service.
/// One example of this is `shuttle-qdrant`.
///
/// Please refer to `shuttle-examples/custom-resource` for examples of how to create a custom resource. For more advanced provisioning
/// of custom resources, please [get in touch](https://discord.gg/shuttle) and detail your use case. We'll be interested to see what you
/// want to provision and how to do it on your behalf on the fly.
#[async_trait]
pub trait ResourceInputBuilder: Default {
/// The input for requesting this resource.
///
/// If the input is a [`shuttle_common::resource::ProvisionResourceRequest`],
/// then the resource will be provisioned and the [`ResourceInputBuilder::Output`]
/// will be a [`shuttle_common::resource::ShuttleResourceOutput<T>`] with the resource's associated output type.
type Input: Serialize + DeserializeOwned;
/// The output from provisioning this resource.
///
/// For custom resources that don't provision anything from Shuttle,
/// this should be the same type as [`ResourceInputBuilder::Input`].
///
/// This type must implement [`IntoResource`] for the desired final resource type `R`.
type Output: Serialize + DeserializeOwned;
/// Construct this resource config. The [`ResourceFactory`] provides access to secrets and metadata.
async fn build(self, factory: &ResourceFactory) -> Result<Self::Input, crate::Error>;
}
/// A factory for getting metadata when building resources
pub struct ResourceFactory {
project_name: String,
secrets: BTreeMap<String, Secret<String>>,
env: Environment,
}
impl ResourceFactory {
pub fn new(
project_name: String,
secrets: BTreeMap<String, Secret<String>>,
env: Environment,
) -> Self {
Self {
project_name,
secrets,
env,
}
}
pub fn get_secrets(&self) -> BTreeMap<String, Secret<String>> {
self.secrets.clone()
}
pub fn get_metadata(&self) -> DeploymentMetadata {
DeploymentMetadata {
env: self.env,
project_name: self.project_name.to_string(),
storage_path: PathBuf::from(STORAGE_DIRNAME),
}
}
}
/// Implement this on an [`ResourceInputBuilder::Output`] type to turn the
/// base resource into the end type exposed to the Shuttle main function.
#[async_trait]
pub trait IntoResource<R>: Serialize + DeserializeOwned {
/// Initialize any logic for creating the final resource of type `R` from the base resource.
///
/// Example: turn a connection string into a connection pool.
async fn into_resource(self) -> Result<R, crate::Error>;
}
// Base impls for [`ResourceInputBuilder::Output`] types that don't need to convert into anything else
#[async_trait]
impl<R: Serialize + DeserializeOwned + Send> IntoResource<R> for R {
async fn into_resource(self) -> Result<R, crate::Error> {
Ok(self)
}
}
#[async_trait]
impl<R: Serialize + DeserializeOwned + Send> IntoResource<R> for ShuttleResourceOutput<R> {
async fn into_resource(self) -> Result<R, crate::Error> {
Ok(self.output)
}
}
/// The core trait of the Shuttle platform. Every service deployed to Shuttle needs to implement this trait.
///
/// An `Into<Service>` implementor is what is returned in the `shuttle_runtime::main` macro
/// in order to run it on the Shuttle servers.
#[async_trait]
pub trait Service: Send {
/// This function is run exactly once on startup of a deployment.
///
/// The passed [`SocketAddr`] receives proxied HTTP traffic from you Shuttle subdomain (or custom domain).
/// Binding to the address is only relevant if this service is an HTTP server.
async fn bind(mut self, addr: SocketAddr) -> Result<(), error::Error>;
}