systemprompt_models/config/
validation.rs1use anyhow::Result;
2use systemprompt_traits::validation_report::{
3 ValidationError, ValidationReport, ValidationWarning,
4};
5
6use crate::profile::Profile;
7
8pub fn validate_profile_paths(profile: &Profile, profile_path: &str) -> ValidationReport {
9 let mut report = ValidationReport::new("paths");
10
11 validate_required_path(&mut report, "system", &profile.paths.system);
12 validate_required_path(&mut report, "services", &profile.paths.services);
13 validate_required_path(&mut report, "bin", &profile.paths.bin);
14
15 validate_required_path(&mut report, "skills", &profile.paths.skills());
16 validate_required_path(&mut report, "config", &profile.paths.config());
17 validate_required_path(&mut report, "web_path", &profile.paths.web_path_resolved());
18 validate_required_path(&mut report, "web_config", &profile.paths.web_config());
19 validate_required_path(&mut report, "web_metadata", &profile.paths.web_metadata());
20 validate_required_path(
21 &mut report,
22 "content_config",
23 &profile.paths.content_config(),
24 );
25
26 validate_optional_path(
27 &mut report,
28 "geoip_database",
29 profile.paths.geoip_database.as_ref(),
30 );
31 validate_optional_path(&mut report, "storage", profile.paths.storage.as_ref());
32
33 let _ = profile_path;
34 report
35}
36
37pub fn validate_required_path(report: &mut ValidationReport, field: &str, path: &str) {
39 if path.is_empty() {
40 report.add_error(
41 ValidationError::new(format!("paths.{}", field), "Required path not configured")
42 .with_suggestion(format!(
43 "Add paths.{} to your profile or run 'systemprompt cloud config'",
44 field
45 )),
46 );
47 return;
48 }
49
50 let path_buf = std::path::Path::new(path);
51 if !path_buf.exists() {
52 report.add_error(
53 ValidationError::new(format!("paths.{}", field), "Path does not exist")
54 .with_path(path)
55 .with_suggestion("Create the directory/file or update the path in your profile"),
56 );
57 }
58}
59
60pub fn validate_required_optional_path(
61 report: &mut ValidationReport,
62 field: &str,
63 path: Option<&String>,
64) {
65 match path {
66 None => {
67 report.add_error(
68 ValidationError::new(format!("paths.{}", field), "Required path not configured")
69 .with_suggestion(format!(
70 "Add paths.{} to your profile or run 'systemprompt cloud config'",
71 field
72 )),
73 );
74 },
75 Some(p) if p.is_empty() => {
76 report.add_error(
77 ValidationError::new(format!("paths.{}", field), "Path is empty").with_suggestion(
78 format!("Set a valid path for paths.{} in your profile", field),
79 ),
80 );
81 },
82 Some(p) => {
83 let path_buf = std::path::Path::new(p);
84 if !path_buf.exists() {
85 report.add_error(
86 ValidationError::new(format!("paths.{}", field), "Path does not exist")
87 .with_path(p)
88 .with_suggestion("Create the directory/file or update the path"),
89 );
90 }
91 },
92 }
93}
94
95pub fn validate_optional_path(report: &mut ValidationReport, field: &str, path: Option<&String>) {
96 if let Some(p) = path {
97 if !p.is_empty() {
98 let path_buf = std::path::Path::new(p);
99 if !path_buf.exists() {
100 report.add_warning(
101 ValidationWarning::new(
102 format!("paths.{}", field),
103 format!("Path does not exist: {}", p),
104 )
105 .with_suggestion("Create the path or remove the config entry"),
106 );
107 }
108 }
109 }
110}
111
112pub fn format_path_errors(report: &ValidationReport, profile_path: &str) -> String {
113 let mut output = String::new();
114 output.push_str("Profile Path Validation Failed\n\n");
115 output.push_str(&format!("Profile: {}\n\n", profile_path));
116 output.push_str("ERRORS:\n\n");
117
118 for error in &report.errors {
119 output.push_str(&format!("[{}] {}\n", report.domain, error.field));
120 output.push_str(&format!(" {}\n", error.message));
121 if let Some(ref path) = error.path {
122 output.push_str(&format!(" Path: {}\n", path.display()));
123 }
124 if let Some(ref suggestion) = error.suggestion {
125 output.push_str(&format!(" To fix: {}\n", suggestion));
126 }
127 output.push('\n');
128 }
129
130 if !report.warnings.is_empty() {
131 output.push_str("WARNINGS:\n\n");
132 for warning in &report.warnings {
133 output.push_str(&format!("[{}] {}\n", report.domain, warning.field));
134 output.push_str(&format!(" {}\n", warning.message));
135 if let Some(ref suggestion) = warning.suggestion {
136 output.push_str(&format!(" To enable: {}\n", suggestion));
137 }
138 output.push('\n');
139 }
140 }
141
142 output
143}
144
145pub fn validate_postgres_url(url: &str) -> Result<()> {
146 if !url.starts_with("postgres://") && !url.starts_with("postgresql://") {
147 return Err(anyhow::anyhow!(
148 "DATABASE_URL must be a PostgreSQL connection string (postgres:// or postgresql://)"
149 ));
150 }
151 Ok(())
152}