systemprompt_config/bootstrap/
profile.rs1use std::path::Path;
11use std::sync::OnceLock;
12
13use systemprompt_manifest::profile::{Profile, ProfileError};
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
36impl ProfileBootstrap {
37 pub fn init() -> ConfigResult<&'static Profile> {
38 if PROFILE.get().is_some() {
39 return Err(ProfileBootstrapError::AlreadyInitialized.into());
40 }
41
42 let path_str = std::env::var("SYSTEMPROMPT_PROFILE")
43 .map_err(|_e| ProfileBootstrapError::PathNotSet)?;
44 let path = std::path::PathBuf::from(path_str);
45
46 let profile = Self::load_from_path_and_validate(&path)?;
47
48 PROFILE_PATH
49 .set(path.to_string_lossy().to_string())
50 .map_err(|_e| ProfileBootstrapError::AlreadyInitialized)?;
51
52 PROFILE
53 .set(profile)
54 .map_err(|_e| ProfileBootstrapError::AlreadyInitialized)?;
55
56 PROFILE
57 .get()
58 .ok_or_else(|| ProfileBootstrapError::NotInitialized.into())
59 }
60
61 pub fn get() -> Result<&'static Profile, ProfileBootstrapError> {
62 PROFILE.get().ok_or(ProfileBootstrapError::NotInitialized)
63 }
64
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 #[must_use]
73 pub fn is_initialized() -> bool {
74 PROFILE.get().is_some()
75 }
76
77 pub fn try_init() -> ConfigResult<&'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) -> ConfigResult<&'static Profile> {
85 if PROFILE.get().is_some() {
86 return Err(ProfileBootstrapError::AlreadyInitialized.into());
87 }
88
89 let profile = Self::load_from_path_and_validate(path)?;
90
91 PROFILE_PATH
92 .set(path.to_string_lossy().to_string())
93 .map_err(|_e| ProfileBootstrapError::AlreadyInitialized)?;
94
95 PROFILE
96 .set(profile)
97 .map_err(|_e| ProfileBootstrapError::AlreadyInitialized)?;
98
99 PROFILE
100 .get()
101 .ok_or_else(|| ProfileBootstrapError::NotInitialized.into())
102 }
103
104 fn load_from_path_and_validate(path: &Path) -> ConfigResult<Profile> {
105 let content = std::fs::read_to_string(path).map_err(|source| ProfileError::ReadFile {
106 path: path.to_path_buf(),
107 source,
108 })?;
109 let profile = Profile::from_yaml(&content, path)?;
110 profile.validate()?;
111 Ok(profile)
112 }
113}