Skip to main content

systemprompt_models/bootstrap/
mod.rs

1//! Bootstrap sequence orchestration.
2//!
3//! This module provides type-safe bootstrap sequencing that enforces
4//! initialization dependencies at compile time. The sequence ensures
5//! that secrets cannot be initialized without a profile, and paths
6//! cannot be initialized without secrets.
7
8use std::marker::PhantomData;
9use std::path::Path;
10
11use anyhow::{Context, Result};
12
13use crate::profile_bootstrap::ProfileBootstrap;
14use crate::secrets_bootstrap::SecretsBootstrap;
15use crate::{AppPaths, Config, PathsConfig};
16
17pub trait BootstrapState {}
18
19#[derive(Debug, Clone, Copy)]
20pub struct Uninitialized;
21impl BootstrapState for Uninitialized {}
22
23#[derive(Debug, Clone, Copy)]
24pub struct ProfileInitialized;
25impl BootstrapState for ProfileInitialized {}
26
27#[derive(Debug, Clone, Copy)]
28pub struct SecretsInitialized;
29impl BootstrapState for SecretsInitialized {}
30
31#[derive(Debug, Clone, Copy)]
32pub struct PathsInitialized;
33impl BootstrapState for PathsInitialized {}
34
35#[derive(Debug)]
36pub struct BootstrapSequence<S: BootstrapState> {
37    _state: PhantomData<S>,
38}
39
40impl Default for BootstrapSequence<Uninitialized> {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl BootstrapSequence<Uninitialized> {
47    #[must_use]
48    pub const fn new() -> Self {
49        Self {
50            _state: PhantomData,
51        }
52    }
53
54    pub fn with_profile(self, path: &Path) -> Result<BootstrapSequence<ProfileInitialized>> {
55        let Self { _state: _ } = self;
56        ProfileBootstrap::init_from_path(path)
57            .with_context(|| format!("Profile initialization failed from: {}", path.display()))?;
58
59        Ok(BootstrapSequence {
60            _state: PhantomData,
61        })
62    }
63
64    #[must_use]
65    pub const fn skip_profile(self) -> Self {
66        self
67    }
68}
69
70impl BootstrapSequence<ProfileInitialized> {
71    pub fn with_secrets(self) -> Result<BootstrapSequence<SecretsInitialized>> {
72        let Self { _state: _ } = self;
73        SecretsBootstrap::init().context("Secrets initialization failed")?;
74
75        Ok(BootstrapSequence {
76            _state: PhantomData,
77        })
78    }
79
80    #[must_use]
81    pub const fn skip_secrets(self) -> Self {
82        self
83    }
84}
85
86impl BootstrapSequence<SecretsInitialized> {
87    pub fn with_paths(self) -> Result<BootstrapSequence<PathsInitialized>> {
88        let Self { _state: _ } = self;
89        let profile = ProfileBootstrap::get()?;
90        AppPaths::init(&profile.paths).context("Failed to initialize paths")?;
91        Config::try_init().context("Failed to initialize configuration")?;
92
93        Ok(BootstrapSequence {
94            _state: PhantomData,
95        })
96    }
97
98    pub fn with_paths_config(
99        self,
100        paths_config: &PathsConfig,
101    ) -> Result<BootstrapSequence<PathsInitialized>> {
102        let Self { _state: _ } = self;
103        AppPaths::init(paths_config).context("Failed to initialize paths")?;
104        Config::try_init().context("Failed to initialize configuration")?;
105
106        Ok(BootstrapSequence {
107            _state: PhantomData,
108        })
109    }
110
111    #[must_use]
112    pub const fn skip_paths(self) -> Self {
113        self
114    }
115}
116
117impl BootstrapSequence<PathsInitialized> {
118    #[must_use]
119    pub const fn complete(self) -> BootstrapComplete {
120        let Self { _state: _ } = self;
121        BootstrapComplete { _private: () }
122    }
123}
124
125#[derive(Debug, Clone, Copy)]
126pub struct BootstrapComplete {
127    _private: (),
128}
129
130pub mod presets {
131    use std::path::Path;
132
133    use anyhow::Result;
134
135    use super::{
136        BootstrapComplete, BootstrapSequence, ProfileInitialized, SecretsInitialized, Uninitialized,
137    };
138
139    pub fn full(profile_path: &Path) -> Result<BootstrapComplete> {
140        Ok(BootstrapSequence::<Uninitialized>::new()
141            .with_profile(profile_path)?
142            .with_secrets()?
143            .with_paths()?
144            .complete())
145    }
146
147    pub fn profile_and_secrets(
148        profile_path: &Path,
149    ) -> Result<BootstrapSequence<SecretsInitialized>> {
150        BootstrapSequence::<Uninitialized>::new()
151            .with_profile(profile_path)?
152            .with_secrets()
153    }
154
155    pub fn profile_only(profile_path: &Path) -> Result<BootstrapSequence<ProfileInitialized>> {
156        BootstrapSequence::<Uninitialized>::new().with_profile(profile_path)
157    }
158}