systemprompt_models/
profile_bootstrap.rs1use 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 pub fn get() -> Result<&'static Profile, ProfileBootstrapError> {
60 PROFILE.get().ok_or(ProfileBootstrapError::NotInitialized)
61 }
62
63 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 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}