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
use std::collections::BTreeMap;
use std::net::SocketAddr;

use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
pub use shuttle_common::{
    database,
    deployment::{DeploymentMetadata, Environment},
    resource,
    secrets::Secret,
    DatabaseInfo, DatabaseResource, DbInput, SecretStore,
};
pub use shuttle_proto::provisioner::{ContainerRequest, ContainerResponse};

pub use crate::error::{CustomError, Error};

#[cfg(feature = "builder")]
pub mod builder;
pub mod error;
#[cfg(feature = "runner")]
pub mod runner;

/// An interface for the provisioner used in [`ResourceBuilder::output`].
#[async_trait]
pub trait Factory: Send + Sync {
    /// Provision a Shuttle database and get the connection information
    async fn get_db_connection(
        &mut self,
        db_type: database::Type,
    ) -> Result<DatabaseInfo, crate::Error>;

    /// Start a Docker container. Only used in local runs.
    async fn get_container(
        &mut self,
        req: ContainerRequest,
    ) -> Result<ContainerResponse, crate::Error>;

    /// Get the secrets associated with this service
    async fn get_secrets(&mut self) -> Result<BTreeMap<String, Secret<String>>, crate::Error>;

    /// Get the metadata for this deployment
    fn get_metadata(&self) -> DeploymentMetadata;
}

/// 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<R>`] to customize the
/// input type `R` that gets into the Shuttle main function on an existing resource.
/// The [`Factory`] in [`ResourceBuilder::output`] can be used to provision resources on Shuttle's servers if your service will need any.
///
/// 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 ResourceBuilder: Default {
    /// The type of resource this plugin creates.
    /// If dealing with a Shuttle-provisioned resource, such as a database, use the corresponding variant.
    /// Otherwise, use the `Custom` variant.
    const TYPE: resource::Type;

    /// The input config to this resource.
    type Config: Serialize;

    /// The output from requesting this resource.
    /// A cached copy of this will be used if the same [`ResourceBuilder::Config`] is found for this [`ResourceBuilder::TYPE`].
    type Output: Serialize + DeserializeOwned;

    /// Get the config of this plugin after it has been built from its macro arguments with the builder pattern.
    ///
    /// If the exact same config was returned by a previous deployment that used this resource, then [`ResourceBuilder::output`]
    /// will not be called to get the builder output again. Rather the output state of the previous deployment
    /// will be passed to [`ResourceBuilder::build`].
    fn config(&self) -> &Self::Config;

    /// Construct this resource with the help of metadata and by calling provisioner methods in the [`Factory`].
    ///
    /// This method is where the actual resource provisioning should take place and is expected to take the longest. It
    /// can at times even take minutes. That is why the output of this method is cached and calling this method can be
    /// skipped as explained in [`ResourceBuilder::config`].
    ///
    /// The output from this function is passed to [`IntoResource::into_resource`].
    async fn output(self, factory: &mut dyn Factory) -> Result<Self::Output, crate::Error>;
}

/// Implement this on an [`ResourceBuilder::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 impl for [`ResourceBuilder::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)
    }
}

/// 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>;
}