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