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