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
37/// Validate a required path (must be set and exist).
38pub 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}