Skip to main content

arcbox_migration/
source.rs

1//! Source discovery for supported migration runtimes.
2
3use crate::error::{MigrationError, Result};
4use crate::model::{SourceConfig, SourceKind};
5use std::path::PathBuf;
6
7/// Source discovery behavior shared by product adapters.
8pub trait MigrationSource {
9    /// Returns the source kind handled by this adapter.
10    fn kind(&self) -> SourceKind;
11
12    /// Returns the default Docker Engine socket path for this source.
13    fn default_socket_path(&self) -> PathBuf;
14}
15
16/// Docker Desktop source adapter.
17#[derive(Debug, Clone, Copy, Default)]
18pub struct DockerDesktopSource;
19
20impl MigrationSource for DockerDesktopSource {
21    fn kind(&self) -> SourceKind {
22        SourceKind::DockerDesktop
23    }
24
25    fn default_socket_path(&self) -> PathBuf {
26        dirs::home_dir()
27            .unwrap_or_else(|| PathBuf::from("/tmp"))
28            .join(".docker")
29            .join("run")
30            .join("docker.sock")
31    }
32}
33
34/// OrbStack source adapter.
35#[derive(Debug, Clone, Copy, Default)]
36pub struct OrbStackSource;
37
38impl MigrationSource for OrbStackSource {
39    fn kind(&self) -> SourceKind {
40        SourceKind::OrbStack
41    }
42
43    fn default_socket_path(&self) -> PathBuf {
44        dirs::home_dir()
45            .unwrap_or_else(|| PathBuf::from("/tmp"))
46            .join(".orbstack")
47            .join("run")
48            .join("docker.sock")
49    }
50}
51
52/// Resolves a supported migration source into a concrete socket path.
53///
54/// # Errors
55///
56/// Returns an error when the selected source socket does not exist.
57pub fn resolve_source(kind: SourceKind, override_socket: Option<PathBuf>) -> Result<SourceConfig> {
58    let path = if let Some(path) = override_socket {
59        path
60    } else {
61        match kind {
62            SourceKind::DockerDesktop => DockerDesktopSource.default_socket_path(),
63            SourceKind::OrbStack => OrbStackSource.default_socket_path(),
64        }
65    };
66
67    if !path.exists() {
68        return Err(MigrationError::MissingSource {
69            kind: kind.as_str(),
70            path,
71        });
72    }
73
74    Ok(SourceConfig {
75        kind,
76        socket_path: path,
77    })
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn docker_desktop_default_socket_ends_with_expected_path() {
86        assert!(
87            DockerDesktopSource
88                .default_socket_path()
89                .ends_with(".docker/run/docker.sock")
90        );
91    }
92
93    #[test]
94    fn orbstack_default_socket_ends_with_expected_path() {
95        assert!(
96            OrbStackSource
97                .default_socket_path()
98                .ends_with(".orbstack/run/docker.sock")
99        );
100    }
101}