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}