Skip to main content

systemprompt_models/config/
validation.rs

1use 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}