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> {
58 PROFILE.get().ok_or(ProfileBootstrapError::NotInitialized)
59 }
60
61 pub fn get_path() -> Result<&'static str, ProfileBootstrapError> {
62 PROFILE_PATH
63 .get()
64 .map(String::as_str)
65 .ok_or(ProfileBootstrapError::NotInitialized)
66 }
67
68 pub fn is_initialized() -> bool {
69 PROFILE.get().is_some()
70 }
71
72 pub fn try_init() -> Result<&'static Profile> {
73 if let Some(profile) = PROFILE.get() {
74 return Ok(profile);
75 }
76 Self::init()
77 }
78
79 pub fn init_from_path(path: &Path) -> Result<&'static Profile> {
80 if PROFILE.get().is_some() {
81 anyhow::bail!(ProfileBootstrapError::AlreadyInitialized);
82 }
83
84 let profile = Self::load_from_path_and_validate(path)
85 .with_context(|| format!("Failed to initialize profile from: {}", path.display()))?;
86
87 PROFILE_PATH
88 .set(path.to_string_lossy().to_string())
89 .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
90
91 PROFILE
92 .set(profile)
93 .map_err(|_| ProfileBootstrapError::AlreadyInitialized)?;
94
95 PROFILE
96 .get()
97 .ok_or_else(|| anyhow::anyhow!(ProfileBootstrapError::NotInitialized))
98 }
99
100 fn load_from_path_and_validate(path: &Path) -> Result<Profile> {
101 let content = std::fs::read_to_string(path)
102 .with_context(|| format!("Failed to read profile: {}", path.display()))?;
103
104 let profile = Profile::parse(&content, path)?;
105 profile.validate()?;
106 Ok(profile)
107 }
108}