shuttle_common/
lib.rs

1pub mod constants;
2#[cfg(feature = "models")]
3pub mod models;
4pub mod secrets;
5#[cfg(feature = "tables")]
6pub mod tables;
7pub mod templates;
8
9use serde::{Deserialize, Serialize};
10
11////// Resource Input/Output types
12
13/// The input given to Shuttle DB resources
14#[derive(Clone, Deserialize, Serialize, Default)]
15pub struct DbInput {
16    pub local_uri: Option<String>,
17    /// Override the default db name. Only applies to RDS.
18    pub db_name: Option<String>,
19}
20
21/// The output produced by Shuttle DB resources
22#[derive(Deserialize, Serialize)]
23#[serde(untagged)]
24pub enum DatabaseResource {
25    ConnectionString(String),
26    Info(DatabaseInfo),
27}
28
29/// Holds the data for building a database connection string.
30#[derive(Clone, Serialize, Deserialize)]
31#[typeshare::typeshare]
32pub struct DatabaseInfo {
33    engine: String,
34    role_name: String,
35    role_password: String,
36    database_name: String,
37    port: String,
38    hostname: String,
39    /// The RDS instance name, which is required for deleting provisioned RDS instances, it's
40    /// optional because it isn't needed for shared PG deletion.
41    instance_name: Option<String>,
42}
43
44impl DatabaseInfo {
45    pub fn new(
46        engine: String,
47        role_name: String,
48        role_password: String,
49        database_name: String,
50        port: String,
51        hostname: String,
52        instance_name: Option<String>,
53    ) -> Self {
54        Self {
55            engine,
56            role_name,
57            role_password,
58            database_name,
59            port,
60            hostname,
61            instance_name,
62        }
63    }
64
65    /// For connecting to the database.
66    pub fn connection_string(&self, show_password: bool) -> String {
67        format!(
68            "{}://{}:{}@{}:{}/{}",
69            self.engine,
70            self.role_name,
71            if show_password {
72                &self.role_password
73            } else {
74                "********"
75            },
76            self.hostname,
77            self.port,
78            self.database_name,
79        )
80    }
81
82    pub fn role_name(&self) -> String {
83        self.role_name.to_string()
84    }
85
86    pub fn database_name(&self) -> String {
87        self.database_name.to_string()
88    }
89
90    pub fn instance_name(&self) -> Option<String> {
91        self.instance_name.clone()
92    }
93}
94
95// Don't leak password in Debug
96impl std::fmt::Debug for DatabaseInfo {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        write!(f, "DatabaseInfo {{ {:?} }}", self.connection_string(false))
99    }
100}
101
102/// Used to request a container from the local run provisioner
103#[derive(Serialize, Deserialize)]
104pub struct ContainerRequest {
105    pub project_name: String,
106    /// Type of container, used in the container name. ex "qdrant"
107    pub container_name: String,
108    /// ex. "qdrant/qdrant:latest"
109    pub image: String,
110    /// The internal port that the container should expose. ex. "6334/tcp"
111    pub port: String,
112    /// list of "KEY=value" strings
113    pub env: Vec<String>,
114}
115
116/// Response from requesting a container from the local run provisioner
117#[derive(Serialize, Deserialize)]
118pub struct ContainerResponse {
119    /// The port that the container exposes to the host.
120    /// Is a string for parity with the Docker respose.
121    pub host_port: String,
122}
123
124/// Check if two versions are compatible based on the rule used by cargo:
125/// "Versions `a` and `b` are compatible if their left-most nonzero digit is the same."
126pub fn semvers_are_compatible(a: &semver::Version, b: &semver::Version) -> bool {
127    if a.major != 0 || b.major != 0 {
128        a.major == b.major
129    } else if a.minor != 0 || b.minor != 0 {
130        a.minor == b.minor
131    } else {
132        a.patch == b.patch
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use std::str::FromStr;
139
140    #[test]
141    fn semver_compatibility_check_works() {
142        let semver_tests = &[
143            ("1.0.0", "1.0.0", true),
144            ("1.8.0", "1.0.0", true),
145            ("0.1.0", "0.2.1", false),
146            ("0.9.0", "0.2.0", false),
147        ];
148        for (version_a, version_b, are_compatible) in semver_tests {
149            let version_a = semver::Version::from_str(version_a).unwrap();
150            let version_b = semver::Version::from_str(version_b).unwrap();
151            assert_eq!(
152                super::semvers_are_compatible(&version_a, &version_b),
153                *are_compatible
154            );
155        }
156    }
157}