Skip to main content

gitway_lib/
config.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2// Rust guideline compliant 2026-03-30
3//! Configuration builder for a Gitway session.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use gitway_lib::GitwayConfig;
9//! use std::time::Duration;
10//!
11//! // Connect to GitHub (default):
12//! let config = GitwayConfig::github();
13//!
14//! // Connect to GitLab:
15//! let config = GitwayConfig::gitlab();
16//!
17//! // Connect to Codeberg:
18//! let config = GitwayConfig::codeberg();
19//!
20//! // Connect to any host with a custom port:
21//! let config = GitwayConfig::builder("git.example.com")
22//!     .port(22)
23//!     .username("git")
24//!     .inactivity_timeout(Duration::from_secs(60))
25//!     .build();
26//! ```
27
28use std::path::PathBuf;
29use std::time::Duration;
30
31use crate::hostkey::{
32    DEFAULT_CODEBERG_HOST, DEFAULT_GITHUB_HOST, DEFAULT_GITLAB_HOST, DEFAULT_PORT,
33    FALLBACK_PORT, GITHUB_FALLBACK_HOST, GITLAB_FALLBACK_HOST,
34};
35
36// ── Public config type ────────────────────────────────────────────────────────
37
38/// Immutable configuration for a [`GitwaySession`](crate::GitwaySession).
39///
40/// Construct via [`GitwayConfig::builder`], or use one of the convenience
41/// constructors ([`github`](Self::github), [`gitlab`](Self::gitlab),
42/// [`codeberg`](Self::codeberg)) for the most common targets.
43#[derive(Debug, Clone)]
44pub struct GitwayConfig {
45    /// Primary SSH host (e.g. `github.com`, `gitlab.com`, `codeberg.org`).
46    pub host: String,
47    /// Primary SSH port (default: 22).
48    pub port: u16,
49    /// Remote username (always `git` for hosted services; FR-13).
50    pub username: String,
51    /// Explicit identity file path supplied via `--identity`.
52    pub identity_file: Option<PathBuf>,
53    /// OpenSSH certificate path supplied via `--cert`.
54    pub cert_file: Option<PathBuf>,
55    /// When `true`, skip host-key verification (FR-8).
56    pub skip_host_check: bool,
57    /// Inactivity timeout for the SSH session (FR-5).
58    ///
59    /// GitHub's idle threshold is around 60 s; this is the configured
60    /// client-side inactivity timeout, not a per-packet deadline.
61    pub inactivity_timeout: Duration,
62    /// Path to a `known_hosts`-style file for custom or self-hosted instances
63    /// (FR-7).  Format: one `hostname SHA256:<fp>` entry per line.
64    pub custom_known_hosts: Option<PathBuf>,
65    /// Enable verbose debug logging when `true`.
66    pub verbose: bool,
67    /// Optional fallback host when port 22 is unavailable (FR-1).
68    ///
69    /// GitHub: `ssh.github.com:443`. GitLab: `altssh.gitlab.com:443`.
70    /// Codeberg has no published port-443 fallback.
71    pub fallback: Option<(String, u16)>,
72}
73
74impl GitwayConfig {
75    /// Begin building a config targeting `host`.
76    ///
77    /// All optional fields default to sensible values. No fallback host is
78    /// set by default; use the provider-specific convenience constructors
79    /// ([`github`](Self::github), [`gitlab`](Self::gitlab)) if you want the
80    /// port-443 fallback pre-configured.
81    pub fn builder(host: impl Into<String>) -> GitwayConfigBuilder {
82        GitwayConfigBuilder::new(host.into())
83    }
84
85    /// Convenience constructor for the default GitHub target (`github.com:22`).
86    ///
87    /// Includes the `ssh.github.com:443` fallback pre-configured.
88    #[must_use]
89    pub fn github() -> Self {
90        Self::builder(DEFAULT_GITHUB_HOST)
91            .fallback(Some((GITHUB_FALLBACK_HOST.to_owned(), FALLBACK_PORT)))
92            .build()
93    }
94
95    /// Convenience constructor for the default GitLab target (`gitlab.com:22`).
96    ///
97    /// Includes the `altssh.gitlab.com:443` fallback pre-configured.
98    #[must_use]
99    pub fn gitlab() -> Self {
100        Self::builder(DEFAULT_GITLAB_HOST)
101            .fallback(Some((GITLAB_FALLBACK_HOST.to_owned(), FALLBACK_PORT)))
102            .build()
103    }
104
105    /// Convenience constructor for Codeberg (`codeberg.org:22`).
106    ///
107    /// Codeberg has no published port-443 SSH fallback; no fallback is set.
108    #[must_use]
109    pub fn codeberg() -> Self {
110        Self::builder(DEFAULT_CODEBERG_HOST).build()
111    }
112}
113
114// ── Builder ───────────────────────────────────────────────────────────────────
115
116/// Builder for [`GitwayConfig`].
117///
118/// Obtained via [`GitwayConfig::builder`].
119#[derive(Debug)]
120#[must_use]
121pub struct GitwayConfigBuilder {
122    host: String,
123    port: u16,
124    username: String,
125    identity_file: Option<PathBuf>,
126    cert_file: Option<PathBuf>,
127    skip_host_check: bool,
128    inactivity_timeout: Duration,
129    custom_known_hosts: Option<PathBuf>,
130    verbose: bool,
131    fallback: Option<(String, u16)>,
132}
133
134impl GitwayConfigBuilder {
135    fn new(host: String) -> Self {
136        Self {
137            host,
138            port: DEFAULT_PORT,
139            username: "git".to_owned(),
140            identity_file: None,
141            cert_file: None,
142            skip_host_check: false,
143            // 60 seconds — large enough to survive slow host responses.
144            // Changing this below ~10 s risks spurious timeouts on congested
145            // links.
146            inactivity_timeout: Duration::from_secs(60),
147            custom_known_hosts: None,
148            verbose: false,
149            // No fallback by default; provider-specific convenience
150            // constructors set this when a known fallback exists.
151            fallback: None,
152        }
153    }
154
155    /// Override the target SSH port (default: 22, FR-1).
156    pub fn port(mut self, port: u16) -> Self {
157        self.port = port;
158        self
159    }
160
161    /// Override the remote username (default: `"git"`, FR-13).
162    pub fn username(mut self, username: impl Into<String>) -> Self {
163        self.username = username.into();
164        self
165    }
166
167    /// Set an explicit identity file path (FR-9 — highest priority).
168    pub fn identity_file(mut self, path: impl Into<PathBuf>) -> Self {
169        self.identity_file = Some(path.into());
170        self
171    }
172
173    /// Set an OpenSSH certificate path (FR-12).
174    pub fn cert_file(mut self, path: impl Into<PathBuf>) -> Self {
175        self.cert_file = Some(path.into());
176        self
177    }
178
179    /// Disable host-key verification.  **Use only for emergencies** (FR-8).
180    pub fn skip_host_check(mut self, skip: bool) -> Self {
181        self.skip_host_check = skip;
182        self
183    }
184
185    /// Override the session inactivity timeout (FR-5).
186    pub fn inactivity_timeout(mut self, timeout: Duration) -> Self {
187        self.inactivity_timeout = timeout;
188        self
189    }
190
191    /// Path to a custom `known_hosts`-style file for self-hosted instances
192    /// (FR-7).
193    pub fn custom_known_hosts(mut self, path: impl Into<PathBuf>) -> Self {
194        self.custom_known_hosts = Some(path.into());
195        self
196    }
197
198    /// Enable verbose debug logging.
199    pub fn verbose(mut self, verbose: bool) -> Self {
200        self.verbose = verbose;
201        self
202    }
203
204    /// Override the fallback host/port.  Pass `None` to disable fallback.
205    pub fn fallback(mut self, fallback: Option<(String, u16)>) -> Self {
206        self.fallback = fallback;
207        self
208    }
209
210    /// Finalise and return the [`GitwayConfig`].
211    #[must_use]
212    pub fn build(self) -> GitwayConfig {
213        GitwayConfig {
214            host: self.host,
215            port: self.port,
216            username: self.username,
217            identity_file: self.identity_file,
218            cert_file: self.cert_file,
219            skip_host_check: self.skip_host_check,
220            inactivity_timeout: self.inactivity_timeout,
221            custom_known_hosts: self.custom_known_hosts,
222            verbose: self.verbose,
223            fallback: self.fallback,
224        }
225    }
226}