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}