systemprompt_loader/
profile_loader.rs1use std::path::Path;
11use systemprompt_identifiers::ProfileName;
12use systemprompt_manifest::Profile;
13use systemprompt_manifest::profile::OVERLAY_PROFILE_FILE;
14
15use crate::error::{ProfileLoadError, ProfileLoadResult};
16
17#[derive(Debug, Clone, Copy)]
18pub struct ProfileLoader;
19
20impl ProfileLoader {
21 pub fn load_from_path(profile_path: &Path) -> ProfileLoadResult<Profile> {
22 Profile::load_from_path(profile_path).map_err(ProfileLoadError::from)
23 }
24
25 pub fn load(profiles_dir: &Path, profile_name: &ProfileName) -> ProfileLoadResult<Profile> {
26 Profile::load_layered(profiles_dir, profile_name.as_str()).map_err(ProfileLoadError::from)
27 }
28
29 pub fn load_from_path_and_validate(profile_path: &Path) -> ProfileLoadResult<Profile> {
30 let profile = Self::load_from_path(profile_path)?;
31 profile.validate().map_err(ProfileLoadError::from)?;
32 Ok(profile)
33 }
34
35 pub fn load_and_validate(
36 profiles_dir: &Path,
37 profile_name: &ProfileName,
38 ) -> ProfileLoadResult<Profile> {
39 let profile = Self::load(profiles_dir, profile_name)?;
40 profile.validate().map_err(ProfileLoadError::from)?;
41 Ok(profile)
42 }
43
44 pub fn save(profile: &Profile, profiles_dir: &Path) -> ProfileLoadResult<()> {
45 let overlay_path = profiles_dir.join(&profile.name).join(OVERLAY_PROFILE_FILE);
46 let header = format!(
47 "# systemprompt.io Profile: {}\n# Overlay over ../base.yaml: holds only what this \
48 environment changes.",
49 profile.display_name
50 );
51 profile
52 .save_overlay(&overlay_path, Some(&header))
53 .map_err(ProfileLoadError::from)
54 }
55
56 #[must_use]
57 pub fn list_available(profiles_dir: &Path) -> Vec<String> {
58 let entries = match std::fs::read_dir(profiles_dir) {
59 Ok(entries) => entries,
60 Err(e) => {
61 if profiles_dir.exists() {
62 tracing::warn!(
63 error = %e,
64 path = %profiles_dir.display(),
65 "Failed to read profiles directory"
66 );
67 }
68 return Vec::new();
69 },
70 };
71 let mut names: Vec<String> = entries
72 .filter_map(Result::ok)
73 .filter(|e| e.path().join(OVERLAY_PROFILE_FILE).is_file())
74 .map(|e| e.file_name().to_string_lossy().to_string())
75 .collect();
76 names.sort();
77 names
78 }
79}