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