Skip to main content

saya_types/
profile.rs

1use serde::{Deserialize, Serialize};
2
3use crate::SqlDialect;
4
5/// A reference to a secret. It is safe to serialize because it holds no value.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum SecretRef {
9    Env { env: String },
10    File { file: String },
11    Keyring { keyring: String },
12}
13
14impl SecretRef {
15    pub fn redacted_label(&self) -> String {
16        match self {
17            Self::Env { env } => format!("env:{env}"),
18            Self::File { .. } => "file:[redacted path]".into(),
19            Self::Keyring { keyring } => format!("keyring:{keyring}"),
20        }
21    }
22}
23
24/// Typed database connection profile loaded from `connections.toml`.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(tag = "type")]
27pub enum DatabaseProfile {
28    #[serde(rename = "postgresql")]
29    Postgres {
30        host: String,
31        port: Option<u16>,
32        database: String,
33        user: String,
34        #[serde(default, rename = "sslmode", alias = "ssl_mode")]
35        ssl_mode: Option<PostgresSslMode>,
36        password: Option<SecretRef>,
37    },
38    #[serde(rename = "mysql")]
39    Mysql {
40        host: String,
41        port: Option<u16>,
42        database: String,
43        user: String,
44        #[serde(default, rename = "sslmode", alias = "ssl_mode")]
45        ssl_mode: Option<MySqlSslMode>,
46        #[serde(default, alias = "ssl_ca")]
47        ssl_ca: Option<SecretRef>,
48        password: Option<SecretRef>,
49    },
50    #[serde(rename = "duckdb")]
51    DuckDb {
52        path: String,
53        read_only: Option<bool>,
54    },
55    #[serde(rename = "snowflake")]
56    Snowflake {
57        account: String,
58        user: String,
59        auth_type: SnowflakeAuth,
60        private_key: Option<SecretRef>,
61        password: Option<SecretRef>,
62        passphrase: Option<SecretRef>,
63        warehouse: Option<String>,
64        database: Option<String>,
65        schema: Option<String>,
66        role: Option<String>,
67    },
68}
69
70/// PostgreSQL TLS verification mode. `None` preserves PostgreSQL's `prefer`
71/// default for existing profiles that omit `sslmode`.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73pub enum PostgresSslMode {
74    #[serde(rename = "disable")]
75    Disable,
76    #[serde(rename = "prefer")]
77    Prefer,
78    #[serde(rename = "require")]
79    Require,
80    #[serde(rename = "verify-ca", alias = "verify_ca")]
81    VerifyCa,
82    #[serde(rename = "verify-full", alias = "verify_full")]
83    VerifyFull,
84}
85
86/// MySQL TLS policy. Omitted legacy profiles are upgraded by the connector to
87/// `VerifyIdentity`; this type deliberately has no downgrade-capable default.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89pub enum MySqlSslMode {
90    #[serde(rename = "disable")]
91    Disable,
92    #[serde(rename = "prefer", alias = "preferred")]
93    Prefer,
94    #[serde(rename = "require")]
95    Require,
96    #[serde(rename = "verify-ca", alias = "verify_ca")]
97    VerifyCa,
98    #[serde(rename = "verify-identity", alias = "verify_identity")]
99    VerifyIdentity,
100}
101
102impl DatabaseProfile {
103    pub const fn dialect(&self) -> SqlDialect {
104        match self {
105            Self::Postgres { .. } => SqlDialect::Postgres,
106            Self::Mysql { .. } => SqlDialect::Mysql,
107            Self::DuckDb { .. } => SqlDialect::DuckDb,
108            Self::Snowflake { .. } => SqlDialect::Snowflake,
109        }
110    }
111}
112
113/// Supported Snowflake authentication flows; browser SSO remains interactive-only.
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum SnowflakeAuth {
117    Keypair,
118    Userpass,
119    Externalbrowser,
120}