shuttle_service/
lib.rs

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