Skip to main content

systemprompt_models/
profile_bootstrap.rs

1use anyhow::{Context, Result};
2use std::path::Path;
3use std::sync::OnceLock;
4
5use crate::profile::Profile;
6
7static PROFILE: OnceLock<Profile> = OnceLock::new();
8static PROFILE_PATH: OnceLock<String> = OnceLock::new();
9
10#[derive(Debug, Clone, Copy)]
11pub struct ProfileBootstrap;
12
13#[derive(Debug, thiserror::Error)]
14pub enum ProfileBootstrapError {
15    #[error("Profile not initialized. Call ProfileBootstrap::init() at application startup")]
16    NotInitialized,
17
18    #[error("Profile already initialized")]
19    AlreadyInitialized,
20
21    #[error("Profile path not set. Set SYSTEMPROMPT_PROFILE environment variable")]
22    PathNotSet,
23
24    #[error("Profile validation failed: {0}")]
25    ValidationFailed(String),
26
27    #[error("Failed to load profile: {0}")]
28    LoadFailed(String),
29}
30
31impl ProfileBootstrap {
32    pub fn init() -> Result<&'static Profile> {
33        if PROFILE.get().is_some() {
34            anyhow::bail!(ProfileBootstrapError::AlreadyInitialized);
35        }
36
37        let path_str =
38            std::env::var("SYSTEMPROMPT_PROFILE").map_err(|_| ProfileBootstrapError::PathNotSet)?;
39        let path = std::path::PathBuf::from(path_str);
40
41        let profile = Self::load_from_path_and_validate(&path)
42            .with_context(|| format!("Failed to initialize profile from: {}", path.display()))?;
43
44        PROFILE_PATH
45            .set(path.to_string_lossy().to_string())
46            .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
47
48        PROFILE
49            .set(profile)
50            .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
51
52        PROFILE
53            .get()
54            .ok_or_else(|| anyhow::anyhow!(ProfileBootstrapError::NotInitialized))
55    }
56
57    /// Get the initialized profile.
58    /// Returns error if profile has not been initialized.
59    pub fn get() -> Result<&'static Profile, ProfileBootstrapError> {
60        PROFILE.get().ok_or(ProfileBootstrapError::NotInitialized)
61    }
62
63    /// Get the initialized profile path.
64    /// Returns error if profile has not been initialized.
65    pub fn get_path() -> Result<&'static str, ProfileBootstrapError> {
66        PROFILE_PATH
67            .get()
68            .map(String::as_str)
69            .ok_or(ProfileBootstrapError::NotInitialized)
70    }
71
72    /// Check if the profile has been initialized.
73    pub fn is_initialized() -> bool {
74        PROFILE.get().is_some()
75    }
76
77    pub fn try_init() -> Result<&'static Profile> {
78        if let Some(profile) = PROFILE.get() {
79            return Ok(profile);
80        }
81        Self::init()
82    }
83
84    pub fn init_from_path(path: &Path) -> Result<&'static Profile> {
85        if PROFILE.get().is_some() {
86            anyhow::bail!(ProfileBootstrapError::AlreadyInitialized);
87        }
88
89        let profile = Self::load_from_path_and_validate(path)
90            .with_context(|| format!("Failed to initialize profile from: {}", path.display()))?;
91
92        PROFILE_PATH
93            .set(path.to_string_lossy().to_string())
94            .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
95
96        PROFILE
97            .set(profile)
98            .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
99
100        PROFILE
101            .get()
102            .ok_or_else(|| anyhow::anyhow!(ProfileBootstrapError::NotInitialized))
103    }
104
105    fn load_from_path_and_validate(path: &Path) -> Result<Profile> {
106        let content = std::fs::read_to_string(path)
107            .with_context(|| format!("Failed to read profile: {}", path.display()))?;
108
109        let profile = Profile::parse(&content, path)?;
110        profile.validate()?;
111        Ok(profile)
112    }
113}