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) {
38 if path.is_empty() {
39 report.add_error(
40 ValidationError::new(format!("paths.{}", field), "Required path not configured")
41 .with_suggestion(format!(
42 "Add paths.{} to your profile or run 'systemprompt cloud config'",
43 field
44 )),
45 );
46 return;
47 }
48
49 let path_buf = std::path::Path::new(path);
50 if !path_buf.exists() {
51 report.add_error(
52 ValidationError::new(format!("paths.{}", field), "Path does not exist")
53 .with_path(path)
54 .with_suggestion("Create the directory/file or update the path in your profile"),
55 );
56 }
57}
58
59pub fn validate_required_optional_path(
60 report: &mut ValidationReport,
61 field: &str,
62 path: Option<&String>,
63) {
64 match path {
65 None => {
66 report.add_error(
67 ValidationError::new(format!("paths.{}", field), "Required path not configured")
68 .with_suggestion(format!(
69 "Add paths.{} to your profile or run 'systemprompt cloud config'",
70 field
71 )),
72 );
73 },
74 Some(p) if p.is_empty() => {
75 report.add_error(
76 ValidationError::new(format!("paths.{}", field), "Path is empty").with_suggestion(
77 format!("Set a valid path for paths.{} in your profile", field),
78 ),
79 );
80 },
81 Some(p) => {
82 let path_buf = std::path::Path::new(p);
83 if !path_buf.exists() {
84 report.add_error(
85 ValidationError::new(format!("paths.{}", field), "Path does not exist")
86 .with_path(p)
87 .with_suggestion("Create the directory/file or update the path"),
88 );
89 }
90 },
91 }
92}
93
94pub fn validate_optional_path(report: &mut ValidationReport, field: &str, path: Option<&String>) {
95 if let Some(p) = path {
96 if !p.is_empty() {
97 let path_buf = std::path::Path::new(p);
98 if !path_buf.exists() {
99 report.add_warning(
100 ValidationWarning::new(
101 format!("paths.{}", field),
102 format!("Path does not exist: {}", p),
103 )
104 .with_suggestion("Create the path or remove the config entry"),
105 );
106 }
107 }
108 }
109}
110
111pub fn format_path_errors(report: &ValidationReport, profile_path: &str) -> String {
112 let mut output = String::new();
113 output.push_str("Profile Path Validation Failed\n\n");
114 output.push_str(&format!("Profile: {}\n\n", profile_path));
115 output.push_str("ERRORS:\n\n");
116
117 for error in &report.errors {
118 output.push_str(&format!("[{}] {}\n", report.domain, error.field));
119 output.push_str(&format!(" {}\n", error.message));
120 if let Some(ref path) = error.path {
121 output.push_str(&format!(" Path: {}\n", path.display()));
122 }
123 if let Some(ref suggestion) = error.suggestion {
124 output.push_str(&format!(" To fix: {}\n", suggestion));
125 }
126 output.push('\n');
127 }
128
129 if !report.warnings.is_empty() {
130 output.push_str("WARNINGS:\n\n");
131 for warning in &report.warnings {
132 output.push_str(&format!("[{}] {}\n", report.domain, warning.field));
133 output.push_str(&format!(" {}\n", warning.message));
134 if let Some(ref suggestion) = warning.suggestion {
135 output.push_str(&format!(" To enable: {}\n", suggestion));
136 }
137 output.push('\n');
138 }
139 }
140
141 output
142}
143
144pub fn validate_postgres_url(url: &str) -> Result<()> {
145 if !url.starts_with("postgres://") && !url.starts_with("postgresql://") {
146 return Err(anyhow::anyhow!(
147 "DATABASE_URL must be a PostgreSQL connection string (postgres:// or postgresql://)"
148 ));
149 }
150 Ok(())
151}