systemprompt_loader/
profile_loader.rs1use std::path::Path;
14use systemprompt_identifiers::ProfileName;
15use systemprompt_manifest::Profile;
16use systemprompt_manifest::profile::ProfileError;
17
18use crate::error::{ProfileLoadError, ProfileLoadResult};
19
20#[derive(Debug, Clone, Copy)]
21pub struct ProfileLoader;
22
23impl ProfileLoader {
24 pub fn load_from_path(profile_path: &Path) -> ProfileLoadResult<Profile> {
25 let content =
26 std::fs::read_to_string(profile_path).map_err(|source| ProfileError::ReadFile {
27 path: profile_path.to_path_buf(),
28 source,
29 })?;
30 Profile::from_yaml(&content, profile_path).map_err(ProfileLoadError::from)
31 }
32
33 pub fn load(services_path: &Path, profile_name: &ProfileName) -> ProfileLoadResult<Profile> {
34 let profile_path = services_path
35 .join("profiles")
36 .join(format!("{profile_name}.secrets.profile.yaml"));
37
38 Self::load_from_path(&profile_path)
39 }
40
41 pub fn load_from_path_and_validate(profile_path: &Path) -> ProfileLoadResult<Profile> {
42 let profile = Self::load_from_path(profile_path)?;
43 profile.validate().map_err(ProfileLoadError::from)?;
44 Ok(profile)
45 }
46
47 pub fn load_and_validate(
48 services_path: &Path,
49 profile_name: &ProfileName,
50 ) -> ProfileLoadResult<Profile> {
51 let profile = Self::load(services_path, profile_name)?;
52 profile.validate().map_err(ProfileLoadError::from)?;
53 Ok(profile)
54 }
55
56 pub fn save(profile: &Profile, services_path: &Path) -> ProfileLoadResult<()> {
57 let profiles_dir = services_path.join("profiles");
58 std::fs::create_dir_all(&profiles_dir).map_err(|e| ProfileLoadError::Io {
59 path: profiles_dir.clone(),
60 source: e,
61 })?;
62
63 let profile_path = profiles_dir.join(format!("{}.secrets.profile.yaml", profile.name));
64 let content = profile.to_yaml().map_err(ProfileLoadError::from)?;
65
66 let content_with_header = format!(
67 "# systemprompt.io Profile: {}\n# \n# WARNING: This file contains secrets.\n# DO NOT \
68 commit to version control.\n\n{content}",
69 profile.display_name
70 );
71
72 std::fs::write(&profile_path, content_with_header).map_err(|e| ProfileLoadError::Io {
73 path: profile_path,
74 source: e,
75 })
76 }
77
78 #[must_use]
79 pub fn list_available(services_path: &Path) -> Vec<String> {
80 let profiles_dir = services_path.join("profiles");
81
82 if !profiles_dir.exists() {
83 return Vec::new();
84 }
85
86 match std::fs::read_dir(&profiles_dir) {
87 Ok(entries) => entries
88 .filter_map(Result::ok)
89 .filter_map(|e| {
90 let name = e.file_name().to_string_lossy().to_string();
91 name.strip_suffix(".secrets.profile.yaml")
92 .map(str::to_owned)
93 })
94 .collect(),
95 Err(e) => {
96 tracing::warn!(
97 error = %e,
98 path = %profiles_dir.display(),
99 "Failed to read profiles directory"
100 );
101 Vec::new()
102 },
103 }
104 }
105}